1#############################################################################
2##
3##  morphisms.gi              FinInG package
4##                                                              John Bamberg
5##                                                              Anton Betten
6##                                                              Jan De Beule
7##                                                             Philippe Cara
8##                                                            Michel Lavrauw
9##                                                           Max Neunhoeffer
10##
11##  Copyright 2018	Colorado State University
12##                  Sabancı Üniversitesi
13##					Università degli Studi di Padova
14##					Universiteit Gent
15##					University of St. Andrews
16##					University of Western Australia
17##                  Vrije Universiteit Brussel
18##
19##
20##  Implementation stuff for incidence geometry morphisms
21##
22#############################################################################
23
24
25########################################
26# 20/3/14
27# cmat notice: in many operations we will compute
28# a matrix to initialize a form. Forms can not handle cmats, so
29# Unpacking the cmats will sometimes be necessary.
30########################################
31
32
33############################################################
34## Generic constructor operations, not intended for the user.
35############################################################
36
37## The three methods for GeometryMorphismByFunction are analogous
38## with the MappingByFunction function. It behaves in exactly the same
39## way except that we return an IsGeometryMorphism.
40
41# CHECKED 27/09/11 jdb
42#############################################################################
43#O  GeometryMorphismByFunction( <els1>, <els2>, <fun>, <bool>, <prefun> )
44##
45InstallMethod( GeometryMorphismByFunction,
46	"for two times a collection of elements of any type, a function, a boolean, and a function",
47	[ IsAnyElementsOfIncidenceStructure, IsAnyElementsOfIncidenceStructure,
48	IsFunction, IsBool, IsFunction ],
49	function( els1, els2, fun, bool, prefun )
50		local morphism;
51		morphism := MappingByFunction(els1, els2, fun, bool, prefun );
52		SetFilterObj( morphism, IsGeometryMorphism );
53		return morphism;
54	end );
55
56# CHECKED 27/09/11 jdb
57#############################################################################
58#O  GeometryMorphismByFunction( <els1>, <els2>, <fun>, <prefun> )
59##
60InstallMethod( GeometryMorphismByFunction,
61	"for two times a collection of elements of any type, and two times a function",
62	[ IsAnyElementsOfIncidenceStructure, IsAnyElementsOfIncidenceStructure,
63    IsFunction, IsFunction ],
64	function( els1, els2, fun, inv )
65		local morphism;
66		morphism := MappingByFunction(els1, els2, fun, inv );
67		SetFilterObj( morphism, IsGeometryMorphism );
68		return morphism;
69	end );
70
71# CHECKED 27/09/11 jdb
72#############################################################################
73#O  GeometryMorphismByFunction( <els1>, <els2>, <fun> )
74##
75InstallMethod( GeometryMorphismByFunction,
76	"for two times a collection of elements of any type, and a function",
77	[ IsAnyElementsOfIncidenceStructure, IsAnyElementsOfIncidenceStructure,
78    IsFunction ],
79	function( els1, els2, fun )
80		local morphism;
81		morphism := MappingByFunction(els1, els2, fun);
82		SetFilterObj( morphism, IsGeometryMorphism );
83		return morphism;
84	end );
85
86#############################################################################
87# Display methods
88#############################################################################
89
90InstallMethod( ViewObj,
91	"for a geometry morphism",
92	[ IsGeometryMorphism ],
93	function( f )
94		Print("<geometry morphism from ");
95		ViewObj(Source(f));
96		Print( " to " );
97		ViewObj(Range(f));
98		Print(">");
99	end );
100
101InstallMethod( PrintObj,
102	"for a geometry morphism",
103	[ IsGeometryMorphism ],
104	function( f )
105		Print("Geometry morphism:\n ", f, "\n");
106	end );
107
108InstallMethod( Display,
109	"for a geometry morphism",
110	[ IsGeometryMorphism ],
111	function( f )
112		Print("Geometry morphism: ", Source(f), " -> ", Range(f), "\n");
113	end );
114
115InstallMethod( ViewObj,
116	"for a geometry morphism",
117	[ IsGeometryMorphism and IsMappingByFunctionWithInverseRep ],
118	function( f )
119		Print("<geometry morphism from ");
120		ViewObj(Source(f));
121		Print( " to " );
122		ViewObj(Range(f));
123		Print(">");
124	end );
125
126InstallMethod( ViewObj,
127	"for a geometry morphism",
128	[ IsGeometryMorphism and IsMappingByFunctionRep ],
129	function( f )
130		Print("<geometry morphism from ");
131		ViewObj(Source(f));
132		Print( " to " );
133		ViewObj(Range(f));
134		Print(">");
135	end );
136
137InstallMethod( PrintObj,
138	"for a geometry morphism",
139	[ IsGeometryMorphism and IsMappingByFunctionRep ],
140	function( f )
141		Print("Geometry morphism:\n ", f, "\n");
142	end );
143
144InstallMethod( Display,
145	"for a geometry morphism",
146	[ IsGeometryMorphism and IsMappingByFunctionRep ],
147	function( f )
148		Print("Geometry morphism: ", Source(f), " -> ", Range(f), "\n");
149	end );
150
151############################################################
152## Generic methods for the Image* operations for IsGeometryMorphism.
153############################################################
154
155# CHECKED 27/09/11 jdb
156#############################################################################
157#O  ImageElm( <em>, <x> )
158##
159InstallOtherMethod( ImageElm,
160	"for a geometry morphism and an element of an incidence structure",
161	[IsGeometryMorphism, IsElementOfIncidenceStructure],
162	function(em, x)
163		return em!.fun(x);
164	end );
165
166# CHECKED 27/09/11 jdb
167#############################################################################
168#O  \^( <x>, <em> )
169##
170InstallOtherMethod( \^,
171	"for an element of an incidence structure and a geometry morphism",
172	[IsElementOfIncidenceStructure, IsGeometryMorphism],
173	function(x, em)
174		return ImageElm(em,x);
175	end );
176
177
178# CHECKED 27/09/11 jdb
179#############################################################################
180#O  ImagesSet( <em>, <x> )
181##
182InstallOtherMethod( ImagesSet,
183	"for a geometry morphism and a collection of elements of an incidence structure",
184	[IsGeometryMorphism, IsElementOfIncidenceStructureCollection],
185	function(em, x)
186		return List(x, t -> em!.fun(t));
187	end );
188
189
190# CHECKED 27/09/11 jdb
191#############################################################################
192#O  PreImageElm( <em>, <x> )
193##
194InstallOtherMethod( PreImageElm,
195	"for a geometry morphism and an element of an incidence structure",
196	[IsGeometryMorphism, IsElementOfIncidenceStructure],
197	function(em, x)
198		if IsInjective(em) then
199			return em!.prefun(x);
200		else
201			Error("Map is not injective");
202		fi;
203	end );
204
205# CHECKED 27/09/11 jdb
206#############################################################################
207#O  PreImagesSet( <em>, <x> )
208##
209InstallOtherMethod( PreImagesSet,
210	"for a geometry morphism and an element of an incidence structure",
211	[IsGeometryMorphism, IsElementOfIncidenceStructureCollection],
212	function(em, x)
213		return List(x, t -> em!.prefun(t));
214	end );
215
216##########################################################
217## User methods for the "natural geometry morphisms"
218##########################################################
219
220## The specialised operations...
221
222# CHECKED 14/12/11 jdb + ml (and added a check that basefields of <ps1> and <ps2> are equal.)
223# cmat changed 21/3/14.
224#############################################################################
225#O  NaturalEmbeddingBySubspace( <ps1>, <ps2>, <v> ) returns a geometry morphism
226# from the projective space <ps1> into <v>, an element of the projective space
227# <ps2>. <v> must be of the right type, i.e. same projective dimension as <ps1>
228# and <ps1> and <ps2> must be projective spaces over the same field.
229##
230InstallMethod( NaturalEmbeddingBySubspace,
231	"for a projective space into another, via a specified subspace",
232	[ IsProjectiveSpace, IsProjectiveSpace, IsSubspaceOfProjectiveSpace ],
233	function( geom1, geom2, v )
234		local d1, d2, rk, f, invbasis, basis, func, pre, map, morphism, bs;
235		rk := v!.type;
236		basis := Unpack(v!.obj); #cmat change
237		d1 := geom1!.dimension + 1;
238		d2 := geom2!.dimension + 1;
239		f := geom2!.basefield;
240		if not v in geom2 then
241			Error("Subspace is not an element of ", geom2);
242		fi;
243		if d2 < d1 or d1 <> rk then
244			Error("Dimensions are incompatible");
245		fi;
246		if not f = geom1!.basefield then
247			Error("Basefields must be the same");
248		fi;
249    ##    To find the preimage, we first find an invertible matrix C such that
250    ##    [a1,..,ad]B = [a1,...,ad,0,...,0]C (where B is our d x e matrix "basis")
251    ##    is our embedding. We simply extend B to a basis for geom2.
252		bs := BaseSteinitzVectors(Basis(geom2!.vectorspace), basis);
253		invbasis := Inverse(Concatenation(bs!.subspace, bs!.factorspace));
254		#ConvertToMatrixRep(invbasis, f); #useless now.
255		func := x -> VectorSpaceToElement(geom2 , Unpack(x!.obj) * basis); #VectorSpaceToElement makes sure the 2nd arg becomes cmat.
256		pre := function(y)
257			local newy;
258			if not y in v then
259				Error("Applying preimage to an element which is not in the range");
260			fi;
261			newy:= Unpack(y!.obj) * invbasis; #cmat change.
262			if not IsMatrix(newy) then
263				newy := newy{[1..d1]};
264                #ConvertToVectorRepNC(newy, f); #useless now.
265			else
266				newy := newy{[1..Size(newy)]}{[1..d1]};
267				#ConvertToMatrixRepNC(newy, f); #useless now.
268			fi;
269			return VectorSpaceToElement(geom1, newy); #VectorSpaceToElement etc.
270		end;
271		morphism := GeometryMorphismByFunction(ElementsOfIncidenceStructure(geom1),
272                                           ElementsOfIncidenceStructure(geom2),
273                                           func, false, pre );
274		SetIsInjective( morphism, true );
275		return morphism;
276	end );
277
278# CHECKED 27/09/11 jdb + ml
279# cmat changed 21/3/14.
280#############################################################################
281#O  NaturalEmbeddingBySubspaceNC( <ps1>, <ps2>, <v> )
282## This operation is just like its namesake except that it
283## has no checks
284##
285InstallMethod( NaturalEmbeddingBySubspaceNC,
286	"for a projective space into another, via a specified subspace",
287	[ IsProjectiveSpace, IsProjectiveSpace, IsSubspaceOfProjectiveSpace ],
288	function( geom1, geom2, v )
289		local d1, f, invbasis, basis, func, pre, map, morphism, bs;
290		basis := Unpack(v!.obj); #cmat change
291		d1 := geom1!.dimension + 1;
292		f := geom2!.basefield;
293		bs := BaseSteinitzVectors(Basis(geom2!.vectorspace), basis);
294		invbasis := Inverse(Concatenation(bs!.subspace, bs!.factorspace));
295		# ConvertToMatrixRep(invbasis, f); #useless now.
296		func := x -> VectorSpaceToElement(geom2 , x!.obj * basis); #VectorSpaceToElement etc.
297		pre := function(y)
298			local newy;
299			newy:= Unpack(y!.obj) * invbasis; #cmat change
300			if not IsMatrix(newy) then
301				newy := newy{[1..d1]};
302				#ConvertToVectorRepNC(newy, f); useless now.
303			else
304				newy := newy{[1..Size(newy)]}{[1..d1]};
305				#ConvertToMatrixRepNC(newy, f); useless now.
306			fi;
307			return VectorSpaceToElement(geom1, newy); #cfr. supra.
308		end;
309		morphism := GeometryMorphismByFunction(ElementsOfIncidenceStructure(geom1),
310                                           ElementsOfIncidenceStructure(geom2),
311                                           func, false, pre );
312		SetIsInjective( morphism, true );
313		return morphism;
314	end );
315
316# CHECKED 27/09/11 jdb + ml
317# cmat version 20/3/14.
318#############################################################################
319#O  NaturalEmbeddingBySubspace( <ps1>, <ps2>, <v> ) returns a geometry morphism
320# from the polar space <ps1> into <ps2>, a polar space induced as a section of
321# <ps1> and <v>, the latter a subspace of the ambient projective space of <ps1>
322##
323InstallMethod( NaturalEmbeddingBySubspace,
324	"for a polar space into another, via a specified subspace",
325	[ IsClassicalPolarSpace, IsClassicalPolarSpace, IsSubspaceOfProjectiveSpace ],
326	function( geom1, geom2, v )
327	local map, d1, d2, rk, f, i, basis, invbasis, bs, c1, c2, orth, tyv, quad1, quad2,
328		change, perp2, formonv, ses1, ses2, ty1, ty2, newmat, func, pre, invchange;
329		rk := v!.type;
330		ty1	:= PolarSpaceType(geom1);
331		ty2 := PolarSpaceType(geom2);
332		f := geom1!.basefield;
333		d1 := geom1!.dimension;
334		d2 := geom2!.dimension;
335		tyv := TypeOfSubspace( geom2, v );
336    ## Check that fields are the same and v is non-degenerate
337		if geom2!.basefield <> f then
338			Error("fields of both spaces must be the same");
339		fi;
340    #27/9/2011. jdb was wondering on the next 7 lines. Is this not just tyv = "degenerate"?
341	#if not (ty2 = "parabolic" and IsEvenInt(Size(f))) then
342    #  perp2 := Polarity(geom2);
343    #  if perp2(v) in geom2 then
344    #     Error("subspace is degenerate");
345    #     return;
346    #  fi;
347    #fi;
348		if rk > d2 or d1 > d2 then
349			Error("dimensions are incompatible");
350		fi;
351		if tyv = "degenerate" then
352			Error("subspace is degenerate");
353		elif
354			tyv <> ty1 then
355			Error("non-degenerate section is not the same type as ", geom1);
356		fi;
357		orth := ["parabolic", "elliptic", "hyperbolic"];
358		if ty1 = ty2 or (ty1 in orth and ty2 in orth) then
359
360          ## Let B be basis of v. Then the form BM(B^T)^sigma (n.b. sigma
361          ## is a field automorphism), where M is the form for geom2,
362          ## will be equivalent to the form for geom1.
363
364			basis := Unpack(v!.obj); #cmat unpack
365
366          ## As usual we must consider two cases, the quadratic form case
367          ## and the sesquilinear form case. We then obtain a base change matrix c1.
368
369			if HasQuadraticForm( geom2 ) and HasQuadraticForm( geom1 ) then
370				quad1 := QuadraticForm(geom1); #cmat notice: these are never cmats. So keep untouched.
371				quad2 := QuadraticForm(geom2);
372				newmat := basis * quad2!.matrix * TransposedMat(basis);
373				formonv := QuadraticFormByMatrix(newmat, f);
374				c1 := BaseChangeToCanonical( quad1 );
375			else
376				ses1 := SesquilinearForm(geom1);
377				ses2 := SesquilinearForm(geom2);
378				if ty1 = "hermitian" then
379					newmat := basis * ses2!.matrix * (TransposedMat(basis))^CompanionAutomorphism(geom1);
380					formonv := HermitianFormByMatrix(newmat, f);
381				else
382					newmat := basis * ses2!.matrix * TransposedMat(basis);
383					formonv := BilinearFormByMatrix(newmat, f);
384				fi;
385				c1 := BaseChangeToCanonical( ses1 );
386			fi;
387
388       ## Finding isometry from geom1 to polar space defined by formofv:
389
390			c2 := BaseChangeToCanonical( formonv );
391			change := c1^-1 * c2;
392			# ConvertToMatrixRep(change, f); #became useless.
393			invchange := change^-1;
394			bs := BaseSteinitzVectors(Basis(geom2!.vectorspace), basis);
395			invbasis := Inverse(Concatenation(bs!.subspace, bs!.factorspace));
396			# ConvertToMatrixRep(invbasis, f); #same here.
397			func := x -> VectorSpaceToElement( geom2, Unpack(x!.obj) * change * basis);
398			pre := function(y)
399				local newy;
400				if not y in v then
401					Error("Applying preimage to an element which is not in the range");
402				fi;
403				newy:= Unpack(y!.obj) * invbasis;
404				if IsMatrix(newy) then
405					newy := newy{[1..Size(newy)]}{[1..rk]};
406					ConvertToMatrixRepNC(newy, f);
407				else
408					newy := newy{[1..rk]};
409					ConvertToVectorRepNC(newy, f);
410				fi;
411				return VectorSpaceToElement(geom1, newy * invchange);
412			end;
413			map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(geom1),
414                                         ElementsOfIncidenceStructure(geom2),
415                                         func, false, pre );
416			SetIsInjective( map, true );
417		else
418			Error("Polar spaces are not compatible");
419		fi;
420		return map;
421	end );
422
423# CHECKED 28/09/11 jdb
424# cmat comments: see NaturalEmbeddingBySubspace (above).
425#############################################################################
426#O  NaturalEmbeddingBySubspaceNC( <ps1>, <ps2>, <v> )
427## This operation is just like its namesake except that it
428## has no checks
429##
430InstallMethod( NaturalEmbeddingBySubspaceNC,
431	"for a geometry into another, via a specified subspace, no-check version",
432	[ IsClassicalPolarSpace, IsClassicalPolarSpace, IsSubspaceOfProjectiveSpace ],
433	function( geom1, geom2, v )
434		local map, rk, f, i, basis, invbasis, bs, c1, c2, orth, tyv, quad1, quad2,
435          change, perp2, formonv, ses1, ses2, ty1, ty2, newmat, func, pre, invchange;
436		rk := v!.type;
437		ty1 := PolarSpaceType(geom1);
438		ty2 := PolarSpaceType(geom2);
439		f := geom1!.basefield;
440		tyv := TypeOfSubspace( geom2, v );
441		orth := ["parabolic", "elliptic", "hyperbolic"];
442		if ty1 = ty2 or (ty1 in orth and ty2 in orth) then
443
444          ## Let B be basis of v. Then the form BM(B^T)^sigma (n.b. sigma
445          ## is a field automorphism), where M is the form for geom2,
446          ## will be equivalent to the form for geom1.
447
448		basis := Unpack(v!.obj);
449
450          ## As usual we must consider two cases, the quadratic form case
451          ## and the sesquilinear form case. We then obtain a base change matrix c1.
452
453		if HasQuadraticForm( geom2 ) and HasQuadraticForm( geom1 ) then
454			quad1 := QuadraticForm(geom1);
455			quad2 := QuadraticForm(geom2);
456			newmat := basis * quad2!.matrix * TransposedMat(basis);
457			formonv := QuadraticFormByMatrix(newmat, f);
458			c1 := BaseChangeToCanonical( quad1 );
459		else
460			ses1 := SesquilinearForm(geom1);
461			ses2 := SesquilinearForm(geom2);
462			if ty1 = "hermitian" then
463				newmat := basis * ses2!.matrix * (TransposedMat(basis))^CompanionAutomorphism(geom1);
464				formonv := HermitianFormByMatrix(newmat, f);
465			else
466				newmat := basis * ses2!.matrix * TransposedMat(basis);
467				formonv := BilinearFormByMatrix(newmat, f);
468			fi;
469			c1 := BaseChangeToCanonical( ses1 );
470		fi;
471
472       ## Finding isometry from geom1 to polar space defined by formofv:
473
474		c2 := BaseChangeToCanonical( formonv );
475		change := c1^-1 * c2;
476		#ConvertToMatrixRepNC(change, f);
477		invchange := change^-1;
478		bs := BaseSteinitzVectors(Basis(geom2!.vectorspace), basis);
479		invbasis := Inverse(Concatenation(bs!.subspace, bs!.factorspace));
480		#ConvertToMatrixRepNC(invbasis, f);
481
482		func := x -> VectorSpaceToElement( geom2, Unpack(x!.obj) * change * basis);
483		pre := function(y)
484			local newy;
485			newy:= Unpack(y!.obj) * invbasis;
486				if IsMatrix(newy) then
487					newy := newy{[1..Size(newy)]}{[1..rk]};
488					ConvertToMatrixRepNC(newy, f);
489				else
490					newy := newy{[1..rk]};
491					ConvertToVectorRepNC(newy, f);
492				fi;
493				return VectorSpaceToElement(geom1, newy * invchange);
494			end;
495			map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(geom1),
496                                         ElementsOfIncidenceStructure(geom2),
497                                         func, false, pre );
498		SetIsInjective( map, true );
499		else
500			Error("Polar spaces are not compatible");
501		fi;
502		return map;
503	end );
504
505# CHECKED 28/09/11 jdb (and added a check that basefields of <ps1> and <ps2> are equal.)
506#cmat changed 20/3/14.
507#############################################################################
508#O  IsomorphismPolarSpaces( <ps1>, <ps2>, <bool> )
509# returns the coordinate transformation from <ps1> to <ps2> (which must be
510# polar spaces of the same type of course). If <bool> is true, then an intertwiner
511# is computed.
512##
513InstallMethod( IsomorphismPolarSpaces,
514    "for two similar polar spaces and a boolean",
515    [ IsClassicalPolarSpace, IsClassicalPolarSpace, IsBool ],
516	function( ps1, ps2, computeintertwiner )
517		local form1, form2, f, map, c1, c2, change, invchange, hom, ty1, ty2,
518          coll1, coll2, gens1, gens2, x, y, func, inv, twinerfunc, twinerprefun, r1, r2;
519		ty1 := PolarSpaceType( ps1 );
520		ty2 := PolarSpaceType( ps2 );
521		if ty1 = "parabolic" and ty2 = "symplectic" then
522            return IsomorphismPolarSpacesProjectionFromNucleus(ps1, ps2, computeintertwiner);
523        fi;
524        r1 := Rank(ps1);
525        r2 := Rank(ps2);
526        if ty1 <> ty2 or r1 <> r2 then
527			Error("Polar spaces are of different type or of different rank");
528		fi;
529		f := ps1!.basefield;
530		if f <> ps2!.basefield then
531			Error( "<ps1> and <ps2> must be polar spaces over the same field");
532		fi;
533		if IsEvenInt(Size(f)) and ty1 in ["parabolic", "elliptic", "hyperbolic"] then
534			form1 := QuadraticForm( ps1 );
535			form2 := QuadraticForm( ps2 );
536		else
537			form1 := SesquilinearForm( ps1 );
538			form2 := SesquilinearForm( ps2 );
539		fi;
540		c1 := BaseChangeToCanonical( form1 );
541		c2 := BaseChangeToCanonical( form2 );
542		change := NewMatrix(IsCMatRep, ps1!.basefield, ps1!.dimension+1,c1^-1 * c2);  #cmat change here
543		#ConvertToMatrixRep(change, f);
544		invchange := Inverse(change);
545		if ty1 = "hermitian" then
546			change := change^CompanionAutomorphism(ps2);
547			invchange := invchange^CompanionAutomorphism(ps1);
548		fi;
549		func := function(x)
550			return VectorSpaceToElement(ps2, ShallowCopy(x!.obj) * change);
551		end;
552		inv := function(x)
553			return VectorSpaceToElement(ps1, ShallowCopy(x!.obj) * invchange);
554		end;
555		map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps1),
556                                      ElementsOfIncidenceStructure(ps2),
557                                      func, inv);
558
559    # map from gens2 to gens1
560		twinerprefun := function( x )
561			local y;
562				y := MutableCopyMat( x!.mat );
563                #return ProjElWithFrob( change * y * invchange^x!.frob, x!.frob, f );
564                return ProjElWithFrob( change * y * invchange^(x!.frob^-1), x!.frob, f );
565
566			end;
567
568    # map from gens1 to gens2
569		twinerfunc := function( y )
570			local x;
571				x := MutableCopyMat( y!.mat );
572                #return ProjElWithFrob( invchange * x * change^y!.frob, y!.frob, f );
573                return ProjElWithFrob( invchange * x * change^(y!.frob^-1), y!.frob, f );
574			end;
575		if computeintertwiner then
576			if (HasIsCanonicalPolarSpace( ps1 ) and IsCanonicalPolarSpace( ps1 )) or
577				HasCollineationGroup( ps1 ) then
578				coll1 := CollineationGroup(ps1);
579				gens1 := GeneratorsOfGroup( coll1 );
580				gens2 := List(gens1, twinerfunc);
581				coll2 := GroupWithGenerators(gens2);
582				hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
583				SetIntertwiner( map, hom );
584				UseIsomorphismRelation(coll1,coll2);
585			elif (HasIsCanonicalPolarSpace( ps2 ) and IsCanonicalPolarSpace( ps2 )) or
586				HasCollineationGroup( ps2 ) then
587				coll2 := CollineationGroup( ps2 );
588				gens2 := GeneratorsOfGroup( coll2 );
589				gens1 := List(gens2, twinerprefun );
590				coll1 := GroupWithGenerators(gens1);
591				hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
592				SetIntertwiner( map, hom );
593				UseIsomorphismRelation(coll1,coll2);
594			else
595				Info(InfoFinInG, 1, "No intertwiner computed. One of the polar spaces must have a collineation group computed");
596			fi;
597		fi;
598		return map;
599	end );
600
601# CHECKED 28/09/11 jdb
602#############################################################################
603#O  IsomorphismPolarSpaces( <ps1>, <ps2> )
604# returns IsomorphismPolarSpaces( <ps1>, <ps2>, true );
605##
606InstallMethod( IsomorphismPolarSpaces,
607	"for two similar polar spaces",
608	[ IsClassicalPolarSpace, IsClassicalPolarSpace ],
609	function( ps1, ps2 )
610		return IsomorphismPolarSpaces( ps1, ps2, true );
611	end );
612
613# CHECKED 28/09/11 jdb
614#############################################################################
615#O  IsomorphismPolarSpacesNC( <ps1>, <ps2>, <bool> )
616# This operation is just like its namesake except that it
617## has no checks
618InstallMethod( IsomorphismPolarSpacesNC,
619    "for two similar polar spaces and a boolean",
620	[ IsClassicalPolarSpace, IsClassicalPolarSpace, IsBool ],
621	function( ps1, ps2, computeintertwiner )
622		local form1, form2, f, map, c1, c2, change, invchange, hom, ty1, ty2, n,
623          coll1, coll2, gens1, gens2, func, inv, mono, twinerprefun, twinerfunc;
624		ty1 := PolarSpaceType( ps1 );
625		ty2 := PolarSpaceType( ps2 );
626		f := ps1!.basefield;
627		if IsEvenInt(Size(f)) and ty1 in ["parabolic", "elliptic", "hyperbolic"] then
628			form1 := QuadraticForm( ps1 );
629			form2 := QuadraticForm( ps2 );
630		else
631			form1 := SesquilinearForm( ps1 );
632			form2 := SesquilinearForm( ps2 );
633		fi;
634		c1 := BaseChangeToCanonical( form1 );
635		c2 := BaseChangeToCanonical( form2 );
636		change := NewMatrix(IsCMatRep, ps1!.basefield, ps1!.dimension+1,c1^-1 * c2);  #cmat change here
637		#ConvertToMatrixRepNC(change, f);
638		invchange := Inverse(change);
639		func := function(x)
640			return VectorSpaceToElement(ps2, ShallowCopy(x!.obj) * change);
641		end;
642		inv := function(x)
643			return VectorSpaceToElement(ps1, ShallowCopy(x!.obj) * invchange);
644		end;
645		map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps1),
646                                      ElementsOfIncidenceStructure(ps2),
647                                      func, inv);
648    ## Now creating intertwiner...
649	# map from gens2 to gens1
650		twinerprefun := function( x )
651			local y;
652			y := MutableCopyMat( x!.mat );
653                #return ProjElWithFrob( invchange * x * change^y!.frob, y!.frob, f );
654                #return ProjElWithFrob( invchange * x * change^(y!.frob^-1), y!.frob, f );
655                return ProjElWithFrob( change * y * invchange^(x!.frob^-1), x!.frob, f );
656		end;
657
658    # map from gens1 to gens2
659		twinerfunc := function( y )
660			local x;
661            x := MutableCopyMat( y!.mat );
662                #return ProjElWithFrob( invchange * x * change^y!.frob, y!.frob, f );
663                return ProjElWithFrob( invchange * x * change^(y!.frob^-1), y!.frob, f );
664		end;
665		if computeintertwiner then
666			if (HasIsCanonicalPolarSpace( ps1 ) and IsCanonicalPolarSpace( ps1 )) or
667				HasCollineationGroup( ps1 ) then
668				coll1 := CollineationGroup(ps1);
669				gens1 := GeneratorsOfGroup( coll1 );
670				gens2 := List(gens1, twinerfunc);
671				coll2 := GroupWithGenerators(gens2);
672				hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
673				SetIntertwiner( map, hom );
674				UseIsomorphismRelation(coll1,coll2);
675			elif (HasIsCanonicalPolarSpace( ps2 ) and IsCanonicalPolarSpace( ps2 )) or
676				HasCollineationGroup( ps2 ) then
677				coll2 := CollineationGroup( ps2 );
678				gens2 := GeneratorsOfGroup( coll2 );
679				gens1 := List(gens2, twinerprefun );
680				coll1 := GroupWithGenerators(gens1);
681				hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
682				SetIntertwiner( map, hom );
683				UseIsomorphismRelation(coll1,coll2);
684			else
685				Info(InfoFinInG, 1, "No intertwiner computed. One of the polar spaces must have a collineation group computed");
686			fi;
687		fi;
688		return map;
689	end );
690
691# CHECKED 28/09/11 jdb
692#############################################################################
693#O  IsomorphismPolarSpacesNC( <ps1>, <ps2> )
694# returns IsomorphismPolarSpacesNC( <ps1>, <ps2>, true );
695##
696InstallMethod( IsomorphismPolarSpacesNC,
697	"for two similar polar spaces",
698	[ IsClassicalPolarSpace, IsClassicalPolarSpace ],
699	function( ps1, ps2 )
700		return IsomorphismPolarSpacesNC( ps1, ps2, true );
701	end );
702
703###########################################################
704## Field reduction / subfield morphisms. Helper operations.
705############################################################
706
707# CHECKED 28/09/11 jdb
708#############################################################################
709#O  ShrinkMat( <B>, <mat> )
710# returns the preimage of BlownUpMat
711##
712InstallMethod( ShrinkMat,
713	"for a basis and a matrix",
714	[ IsBasis, IsMatrix ],
715	function( B, mat )
716
717  ## THERE WAS A BUG IN THIS FUNCTION: (lavrauw 15/10/2010)
718  ## NOT ALL MATRICES ARE BLOWNUP MATRICES! Here is a new version.
719  ## Here is the explanation that could go into the documentation:
720  ## The result of BlownUpMat(B,A) is the matrix, where each entry a=A_ij is replaced by
721  ## the dxd matrix M_a, representing the linear map x |-> ax with respect to the basis B of
722  ## the field K seen as d-dimensional vectorspace over F. This means that if
723  ## B={b_1,b_2,...,b_d}, then the first row are the coefficients of ab_1 w.r.t. B, and so on.
724  ## Concersely, if we want to implement ShrinkMat, we need to check if the input is
725  ## a blown up matrix.
726  ## This can be done as follows. Let A be a dm x dn matrix. For each dxd block, say M,
727  ## we need to check that the set {b_i^(-1)\sum_{j=1}^{d}m_ij b_j: i\in \{1,..,d\}} has size one
728  ## (Since this would be the a \in K represented as a linear map by M w.r.t. the basis B)
729
730  ## This function is basically the inverse map of
731  ## BlownUpMat.
732
733	local d,vecs,m,n,bmat,blocks,bl,submat,i,j,ij,checklist,row,newmat;
734	d:=Size(B);
735	vecs:=BasisVectors(B);
736	m:=DimensionsMat(mat)[1];
737	n:=DimensionsMat(mat)[2];
738  # First we check if m and n are multiples of d
739	if not (IsInt(m/d) and IsInt(n/d)) then
740		Error("The matrix does not have the right dimensions");
741	fi;
742	blocks:=List(Cartesian([0..m/d-1],[0..n/d-1]),ij->mat{[ij[1]*d+1 .. ij[1]*d+d]}{[ij[2]*d+1 ..ij[2]*d+d]});
743	newmat:=[];
744	for i in [1..m/d] do
745		row:=[];
746		for j in [1..n/d] do
747			#submat:=blocks[(i-1)*d+j]; #wrong line?
748   			submat:=blocks[(i-1)*(m/d)+j];
749			checklist:=List([1..d],i->(vecs[i]^(-1)*(Sum([1..d],j->submat[i][j]*vecs[j]))));
750			if Size(AsSet(checklist))<>1 then
751				Error("The matrix is not a blown up matrix");
752			fi;
753			Add(row,checklist[1]);
754		od;
755		Add(newmat,row);
756	od;
757	return newmat;
758  # DO WE NEED TO USE ConvertToMatrixRepNC ?
759	end);
760
761
762## THE OLD ShrinkMat function:
763# InstallGlobalFunction( ShrinkMat, "preimage of BlownUpMat",
764#  function( B, mat )
765#    local result, vectors, row, i, j, resrow, b;
766#    vectors := BasisVectors( B );
767#    result := [];
768#    b := Size(B);
769#    for i in [1..Size(mat)/b] do
770#        resrow := [];
771#        for j in [1..Size(mat[1])/b] do
772#            row :=  mat[(i-1) * b + 1]{[(j-1) * b + 1 .. j * b]};
773#            Add(resrow, row * vectors);
774#        od;
775#        Add(result, resrow);
776#    od;
777#    ConvertToMatrixRepNC( result );
778#    return result;
779#  end );
780
781InstallMethod( ShrinkMat,
782	"for a field, a subfield and a matrix",
783	[ IsField,IsField, IsMatrix ],
784	function( f1,f2,mat )
785	# This gives the same result as ShrinkMat(B,mat) with B the natural basis returned by
786	# Basis(AsVectorSpace(f2,f1));
787	local B;
788	B:=Basis(AsVectorSpace(f2,f1));
789	return ShrinkMat(B,mat);
790end );
791
792
793#############################################################################
794#O  ShrinkVec( <f1>, <f2>, <v> )
795# f2 is a subfield of f1 and v is vector in a vectorspace V2 over f2
796# return the vector of length d2/t, where d2=dim(V2), and t=[f1:f2]
797# using the natural basis Basis(AsVectorSpace(f2,f1))
798##
799InstallMethod( ShrinkVec,
800	"for a field, a subfield and a vector",
801	[ IsField, IsField, IsVector ],
802	function( f1,f2,v )
803	local t,d1,d2,basvecs;
804	t:=Dimension(AsVectorSpace(f2,f1));
805	d1:=Length(v)/t;
806	if IsInt(d1) then
807		basvecs:=BasisVectors(Basis(AsVectorSpace(f2,f1)));
808		return List([1..d1],i->v{[(i-1)*t+1..i*t]}*basvecs);
809	else
810		Error("The length of v is not divisible by the degree of the field extension");
811	fi;
812end );
813
814#############################################################################
815#O ShrinkVec( <f1>, <f2>, <v>, basis )
816# The same operation as above, but now with a basis as extra argument
817InstallMethod( ShrinkVec,
818	"for a field, a subfield and a vector",
819	[ IsField, IsField, IsVector, IsBasis ],
820	function( f1,f2,v,basis )
821	local t,d1,d2,basvecs;
822	t:=Dimension(AsVectorSpace(f2,f1));
823	d1:=Length(v)/t;
824	if IsInt(d1) then
825		basvecs:=BasisVectors(basis);
826		return List([1..d1],i->v{[(i-1)*t+1..i*t]}*basvecs);
827	else
828		Error("The length of v is not divisible by the degree of the field extension");
829	fi;
830end );
831
832
833
834#############################################################################
835#O  BlownUpSubspaceOfProjectiveSpace( <B>, <pg1> )
836#   blows up a projective space by field reduction.
837##
838InstallMethod( BlownUpProjectiveSpace,
839	"for a basis and a projective space",
840	[ IsBasis, IsProjectiveSpace ],
841	function(basis,pg1)
842	local q,t,r,pg2,mat1,mat2;
843		q:=basis!.q;
844		t:=basis!.d;
845		if not pg1!.basefield=GF(q^t) then
846			Error("The basis and the projective space are not compatible!");
847		fi;
848		r:=Dimension(pg1)+1;
849		pg2:=PG(r*t-1,q);
850		return pg2;
851	end );
852
853#############################################################################
854#O  BlownUpProjectiveSpaceBySubfield( <subfield>, <pg> )
855#   blows up a projective space by field reduction w.r.t. the canonical basis
856##
857InstallMethod( BlownUpProjectiveSpaceBySubfield,
858	"for a field and a projective space",
859	[ IsField, IsProjectiveSpace ],
860	function(subfield,pg)
861	local t,r,field;
862        field:=LeftActingDomain(UnderlyingVectorSpace(pg));
863        if not subfield in Subfields(field) then
864        	Error("The first argument is not a subfield of the basefield of the projective space!");
865        fi;
866        t:=Dimension(AsVectorSpace(subfield,field));
867        r:=Dimension(pg)+1;
868        return PG(r*t-1,Size(subfield));
869  end );
870
871# CHECKED 1/10/11 jdb
872#############################################################################
873#O  BlownUpSubspaceOfProjectiveSpace( <B>, <subspace> )
874#  blows up a subspace of a projective space by field reduction
875##
876InstallMethod( BlownUpSubspaceOfProjectiveSpace,
877	"for a basis and a subspace of a projective space",
878	[ IsBasis, IsSubspaceOfProjectiveSpace ],
879	function(basis,subspace)
880	local pg1,q,t,r,pg2,mat1,mat2;
881		pg1:=AmbientGeometry(subspace);
882		q:=basis!.q;
883		t:=basis!.d;
884		if not pg1!.basefield=GF(q^t) then
885			Error("The basis and the subspace are not compatible!");
886		fi;
887		r:=Dimension(pg1)+1;
888		pg2:=PG(r*t-1,q);
889		mat1:=subspace!.obj;
890		if subspace!.type = 1 then
891			mat1 := [subspace!.obj];
892		fi;
893		mat2:=BlownUpMat(basis,mat1);
894		return VectorSpaceToElement(pg2,mat2);
895	end );
896
897#############################################################################
898#O  BlownUpSubspaceOfProjectiveSpaceBySubfield( <subfield>, <subspace> )
899#	blows up a subspace of projective space by field reduction
900# This is w.r.t. to the canonical basis of the field over the subfield.
901##
902InstallMethod( BlownUpSubspaceOfProjectiveSpaceBySubfield,
903	"for a field and a subspace of a projective space",
904	[ IsField, IsSubspaceOfProjectiveSpace],
905	function(subfield,subspace)
906	local pg,field,basis;
907		pg:=AmbientGeometry(subspace);
908		field:=LeftActingDomain(UnderlyingVectorSpace(pg));
909		if not subfield in Subfields(field) then
910			Error("The first argument is not a subfield of the basefield of the projective space!");
911		fi;
912		basis:=Basis(AsVectorSpace(subfield,field));
913		return BlownUpSubspaceOfProjectiveSpace(basis,subspace);
914	end );
915
916#############################################################################
917#O  IsDesarguesianSpreadElement( <B>, <subspace> )
918# checks if a subspace is a blown up point using field reduction, w.r.t. a basis
919##
920InstallMethod( IsDesarguesianSpreadElement,
921	"for a basis and a subspace of a projective space",
922	[ IsBasis, IsSubspaceOfProjectiveSpace ],
923	function(basis,subspace)
924		local flag,q,t,pg1,pg2,em,basvecs,v,v1,i,mat,rt,r;
925		flag:=true;
926		q:=basis!.q;
927		t:=basis!.d;
928		pg2:=AmbientGeometry(subspace);
929		rt:=Dimension(pg2)+1;
930		if not (Dimension(subspace)+1 = t and IsInt(rt/t)) then
931			flag:=false;
932		else
933			r:=rt/t;
934			pg1:=PG(r-1,q^t);
935			basvecs:=BasisVectors(basis);
936			v:=Coordinates(Random(Points(subspace)));
937			v1:=List([1..r],i->v{[(i-1)*t+1..i*t]}*basvecs);
938			mat:=BlownUpMat(basis,[v1]);
939			flag:=subspace = VectorSpaceToElement(pg2,mat);
940		fi;
941		return flag;
942	end );
943
944# To be done: check this method! (but it is currently never used).
945#############################################################################
946#O  IsBlownUpSubspaceOfProjectiveSpace( <B>, <mat> )
947# checks if a subspace is blown up using field reduction, w.r.t. a basis
948##
949InstallMethod( IsBlownUpSubspaceOfProjectiveSpace,
950	"for a basis and a subspace of a projective space",
951	[ IsBasis, IsSubspaceOfProjectiveSpace ],
952	# It's important that we include a basis in the arguments,
953	# since for every subspace, provided it has the right dimension, there exists some
954	# basis with respect to which the subspace is blown up.
955	function(basis,subspace)
956		local flag,q,F,t,K,pg2,rt,r,pg1,basvecs,mat1,span,x,v,v1,i;
957		flag:=true;
958		q:=basis!.q;
959		F:=GF(q);
960		t:=basis!.d;
961		K:=GF(q^t);
962		pg2:=AmbientGeometry(subspace);
963		rt:=Dimension(pg2)+1;
964		if not (IsInt(rt/t) and IsInt((Dimension(subspace)+1)/t)) then
965			flag:=false;
966		else
967			r:=rt/t;
968			pg1:=PG(r-1,q^t);
969			basvecs:=BasisVectors(basis);
970			mat1:=[];
971			span:=[];
972			repeat
973				repeat x:=Random(Points(subspace)); until not x in span;
974					v:=Coordinates(x);
975					v1:=List([1..r],i->v{[(i-1)*t+1..i*t]}*basvecs);
976					Add(mat1,v1);
977					span:=VectorSpaceToElement(pg2,BlownUpMat(basis,mat1));
978				until Dimension(span)=Dimension(subspace);
979				if not span = subspace then
980					flag:= false;
981				fi;
982		fi;
983		return flag;
984 end );
985
986#############################################################################
987#		PROJECTIVE SPACES
988#############################################################################
989
990#
991#  A recent reference on field reduction of projective spaces is
992#  "Field reduction and linear sets in finite geomety" by Lavrauw
993#   and Van de Voorde (preprint 2013)
994
995#############################################################################
996#O  NaturalEmbeddingByFieldReduction( <geom1>, <field>, <basis> )
997# <geom2> is a projective space over a field K, <geom1> is a projective space
998# over a field extension L, and considering L as a vector space over K, yields
999# that <geom1> and <geom2> have the same ambient vectorspace over K, then this
1000# operation returns the natural embedding, i.e. with relation to the given basis
1001# of L over K.
1002# Important note: the intertwiner has as source the *Projectivity group* only, not
1003# the full collineation group!
1004##
1005InstallMethod( NaturalEmbeddingByFieldReduction,
1006	"for two projective spaces and a basis",
1007     [ IsProjectiveSpace, IsField, IsBasis ],
1008	function( pg1, f2, basis )
1009
1010  ## This morphism contains a func and prefunc with built-in check.
1011
1012		local map, f1, d1, d2, t, fun, prefun, g1, gens, newgens, g2, twiner, hom, hominv, q1, q2, pg2;
1013		f1 := pg1!.basefield;
1014		q1 := Size(f1);
1015		q2 := Size(f2);
1016		t := LogInt(q1,q2);
1017		if not q2^t = q1 then
1018			Error( "<f2> is not a subfield of the base field of <pg1> ");
1019		fi;
1020		d1 := pg1!.dimension + 1;
1021		d2 := d1*t;
1022		pg2 := ProjectiveSpace(d2-1,q2);
1023		if not (IsBasis(basis) and f1=GF((basis!.q)^basis!.d) and f2=GF(basis!.q) and d1*(basis!.d)=d2) then
1024			Error("The basis is not a basis or is not compatible with the basefields of the geometries");
1025		fi;
1026
1027		fun := function( subspace ) # This map blows up a subspace of geom1 to a subspace of geom2
1028            local mat1,mat2;
1029            #return BlownUpSubspaceOfProjectiveSpace(basis,x);
1030            mat1 := subspace!.obj;
1031            if subspace!.type = 1 then
1032                mat1 := [subspace!.obj];
1033            fi;
1034            mat2 := BlownUpMat(basis,mat1);
1035            return VectorSpaceToElement(pg2,mat2);
1036		end;
1037
1038        prefun := function( subspace ) # This map is the inverse of func and returns an error, or a subspace of geom1
1039            local flag,basvecs,mat1,span,x,v,v1,i,vecs;
1040            flag:=true;
1041            if not subspace in pg2 then
1042                Error("The input is not in the range of the field reduction map!");
1043            fi;
1044            if not IsInt((Dimension(subspace)+1)/t) then
1045                flag := false;
1046            else
1047                basvecs:=BasisVectors(basis);
1048                vecs := Unpack(UnderlyingObject(subspace));
1049                mat1 := List(vecs,x->List([1..d1],i->x{[(i-1)*t+1..i*t]}*basvecs));
1050                span := VectorSpaceToElement(pg2,BlownUpMat(basis,mat1));
1051                if not span = subspace then
1052                    flag := false;
1053                fi;
1054            fi;
1055            if flag= false then
1056                Error("The input is not in the range of the field reduction map!");
1057            fi;
1058            return VectorSpaceToElement(pg1,mat1);
1059        end;
1060
1061   		#prefun := function( x ) #I want to find out why this is not correct...
1062		#	return VectorSpaceToElement(pg1,ShrinkMat(basis,x!.obj));
1063		#end;
1064
1065		map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(pg1),
1066                                         ElementsOfIncidenceStructure(pg2),
1067                                            fun, false, prefun);
1068
1069		SetIsInjective( map, true );
1070
1071        ## Now creating intertwiner
1072        ## 12/6/14: added check to see whether m is really a projectivity
1073
1074        hom := function( m )
1075			local image;
1076            if not IsOne(m!.frob) then
1077                return fail;
1078                Info(InfoFinInG, 1, "<el> is not a projectivity");
1079            fi;
1080			image := BlownUpMat(basis, m!.mat);
1081			ConvertToMatrixRepNC( image, f2 );
1082			return CollineationOfProjectiveSpace(image, f2);
1083		end;
1084
1085		hominv := function( m )
1086			local preimage;
1087            if not IsOne(m!.frob) then
1088                return fail;
1089                Info(InfoFinInG, 1, "<el> is not a projectivity");
1090            fi;
1091			preimage := ShrinkMat(basis, Unpack(m!.mat));
1092			ConvertToMatrixRepNC( preimage, f1 );
1093			return CollineationOfProjectiveSpace(preimage, f1);
1094		end;
1095
1096		g1 := ProjectivityGroup( pg1 );
1097		gens := GeneratorsOfGroup( g1 );
1098		newgens := List(gens, hom);
1099		g2 := Group( newgens );
1100		SetSize(g2, Size(g1));
1101		twiner := GroupHomomorphismByFunction(g1, g2, hom, hominv);
1102		SetIntertwiner( map, twiner);
1103		return map;
1104	end );
1105
1106#############################################################################
1107#O  NaturalEmbeddingByFieldReduction( <geom1>, <field> )
1108# <field> is a field K, <geom1> is a projective space
1109# over a field extension L, and considering L as a vector space over K, yields
1110# that a projective space geom2 that has the same ambient vectorspace over K. This
1111# operation returns the natural embedding, i.e. with relation to the standard basis of
1112# L over K.
1113##
1114InstallMethod( NaturalEmbeddingByFieldReduction,
1115	"for a projective space and a field",
1116	[ IsProjectiveSpace, IsField ],
1117	function( pg1, f2 )
1118		local basis;
1119		basis:=Basis(AsVectorSpace(f2,pg1!.basefield));
1120		return NaturalEmbeddingByFieldReduction(pg1,f2,basis);
1121	end );
1122
1123#############################################################################
1124#O  NaturalEmbeddingByFieldReduction( <geom1>, <geom2>, <basis> )
1125# <geom2> is a projective space over a field K, <geom1> is a projective space
1126# over a field extension L, and considering L as a vector space over K, yields
1127# that <geom1> and <geom2> have the same ambient vectorspace over K, then this
1128# operation returns the natural embedding, i.e. with relation to the given basis
1129# of L over K.
1130##
1131InstallMethod( NaturalEmbeddingByFieldReduction,
1132	"for two projective spaces and a basis",
1133	[ IsProjectiveSpace, IsProjectiveSpace, IsBasis ],
1134	function( pg1, pg2, basis )
1135		return NaturalEmbeddingByFieldReduction(pg1,pg2!.basefield,basis);
1136	end);
1137
1138# CHECKED 28/09/11 jdb
1139#############################################################################
1140#O  NaturalEmbeddingByFieldReduction( <geom1>, <geom2> )
1141# <geom2> is a projective space over a field K, <geom1> is a projective space
1142# over a field extension L, and considering L as a vector space over K, yields
1143# that <geom1> and <geom2> have the same ambient vectorspace over K, then this
1144# operation returns the natural embedding, i.e. with relation to the standard basis of
1145# L over K.
1146##
1147InstallMethod( NaturalEmbeddingByFieldReduction,
1148	"for two projective spaces",
1149	[ IsProjectiveSpace, IsProjectiveSpace ],
1150	function( pg1, pg2 )
1151		local basis;
1152		basis:=Basis(AsVectorSpace(pg2!.basefield,pg1!.basefield));
1153		return NaturalEmbeddingByFieldReduction(pg1,pg2!.basefield,basis);
1154	end );
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165############################################################################
1166#		POLAR SPACES
1167##############################################################################
1168
1169
1170#
1171#  Two references on field reduction of polar spaces:
1172#  [1] from group theoretic point of view: "Polar spaces and embeddings of classical groups" by Nick Gill
1173#  (New Zealand J. Math)
1174#  [2] from finite geometry point of view: "Field reduction and linear sets in finite geomety" by Lavrauw
1175#   and Van de Voorde (preprint 2013)
1176#
1177#  We will use [2] as it is more convenient to us.
1178
1179
1180####
1181#
1182# First the field reduction of bilinear forms, quadratic forms and hermitian forms
1183#
1184#####
1185
1186
1187#############################################################################
1188#O  BilinearFormFieldReduction( <bil1>, <f2>, <alpha>, <basis> )
1189# <bil1> is a bilinear form over <f1>, <f2> is a subfield of <f1>.
1190# <alpha> determines the linear map from f1 to <f2>, f1 the field extension of <f2>
1191# with basis <basis>. This operation then
1192# returns the bilinear form L(bil1(.,.)), L(x) = T(alpha*x), T the trace from <f1> to <f2>.
1193##
1194# REMARK (ml 31/10/13) The fourth argument is a basis of L over K. This just gives
1195# extra functionality to the user, and it is used as third argument in the
1196# operation ShrinkVec.
1197# There is also a version of this field reduction operation, which does not use
1198# a basis as fourth argument. In this case the standard GAP basis is used.
1199
1200InstallMethod( BilinearFormFieldReduction,
1201	"for a bilinear form and a field",
1202	[ IsBilinearForm, IsField, IsFFE, IsBasis ],
1203	function(bil1,f2,alpha,basis)
1204	# f2 is a subfield of the basefield of the bilinear form bil1
1205	local mat1,f1,d1,t,d2,V2,V1,phi,b2,b2vecs,mat2,i,row,j,bil2;
1206	mat1:=bil1!.matrix;
1207	f1:=bil1!.basefield;
1208	d1:=Size(mat1);
1209	t:=Dimension(AsVectorSpace(f2,f1));
1210	d2:=d1*t;
1211	V2:=f2^d2;
1212	V1:=f1^d1;
1213	b2:=Basis(V2);
1214	b2vecs:=BasisVectors(b2);
1215	mat2:=[];
1216	for i in [1..d2] do
1217		row:=[];
1218		for j in [1..d2] do
1219			Add(row,Trace(f1,f2,alpha*(ShrinkVec(f1,f2,b2vecs[i],basis)*mat1*ShrinkVec(f1,f2,b2vecs[j],basis))));
1220		od;
1221		Add(mat2,row);
1222	od;
1223	bil2:=BilinearFormByMatrix(mat2,f2);
1224	return bil2;
1225end );
1226
1227#############################################################################
1228#O  BilinearFormFieldReduction( <bil1>, <f2>, <alpha> )
1229# The same as above, but without specified basis. In this case the canonical basis is used.
1230##
1231InstallMethod( BilinearFormFieldReduction,
1232	"for a bilinear form and a field",
1233	[ IsBilinearForm, IsField, IsFFE ],
1234	function(bil1,f2,alpha)
1235	# f2 is a subfield of the basefield of the bilinear form bil1
1236	return BilinearFormFieldReduction(bil1,f2,alpha,Basis(AsVectorSpace(f2,bil1!.basefield)));
1237end );
1238
1239#############################################################################
1240#O  QuadraticFormFieldReduction( <qf1>, <f2>, <alpha>, <basis> )
1241# <bil1> is a bilinear form over <f1>, <f2> is a subfield of <f1>. This operation
1242# returns the bilinear form T(alpha*qf1(.,.)), T the trace from <f1> to <f2>.
1243##
1244# REMARK (ml 31/10/13) The fourth argument is a basis of L over K. This just gives
1245# extra functionality to the user, and it is used as third argument in the
1246# operation ShrinkVec.
1247# There is also a version of this field reduction operation, which does not use
1248# a basis as fourth argument. In this case the standard GAP basis is used.
1249
1250InstallMethod( QuadraticFormFieldReduction,
1251	"for a quadratic form and a field",
1252	[ IsQuadraticForm, IsField, IsFFE, IsBasis ],
1253	function(qf1,f2,alpha,basis)
1254	# f2 is a subfield of the basefield for the quadratic form q1
1255	local f1,basvecs,d1,t,d2,V2,V1,phi,b2,b2vecs,mat2,i,bil1,j,qf2;
1256	f1:=qf1!.basefield;
1257	basvecs:=BasisVectors(basis);
1258	d1:=Size(qf1!.matrix);
1259	t:=Dimension(AsVectorSpace(f2,f1));
1260	d2:=d1*t;
1261	V2:=f2^d2;
1262	V1:=f1^d1;
1263	b2:=Basis(V2);
1264	b2vecs:=BasisVectors(b2);
1265	mat2:=IdentityMat(d2,f2);
1266	for i in [1..d2] do
1267		mat2[i][i]:=Trace(f1,f2,alpha*((ShrinkVec(f1,f2,b2vecs[i],basis))^qf1));
1268	od;
1269	for i in [1..d2-1] do
1270		for j in [i+1..d2] do
1271			mat2[i][j]:=Trace(f1,f2,alpha*((ShrinkVec(f1,f2,b2vecs[i]+b2vecs[j],basis))^qf1
1272							-(ShrinkVec(f1,f2,b2vecs[i],basis))^qf1-(ShrinkVec(f1,f2,b2vecs[j],basis))^qf1));
1273			#mat2[j][i]:=mat2[i][j]; THESE entries need to be zero
1274		od;
1275	od;
1276	qf2:=QuadraticFormByMatrix(mat2,f2);
1277	return qf2;
1278end );
1279
1280#############################################################################
1281# #O  QuadraticFormFieldReduction( <qf1>, <f2>, <alpha> )
1282# The same as above, but without specified basis. In this case the canonical basis is used.
1283##
1284InstallMethod( QuadraticFormFieldReduction,
1285	"for a quadratic form and a field",
1286	[ IsQuadraticForm, IsField, IsFFE ],
1287	function(qf1,f2,alpha)
1288	local basis,qf2;
1289	# f2 is a subfield of the basefield for the quadratic form q1
1290	basis:=Basis(AsVectorSpace(f2,qf1!.basefield));
1291	qf2:=QuadraticFormFieldReduction(qf1,f2,alpha,basis);
1292	return qf2;
1293end );
1294
1295
1296#############################################################################
1297#O  HermitianFormFieldReduction( <hf1>, <f2>, <alpha>, <basis> )
1298# <hf1> is a hermitian form over <f1>, <f2> is a subfield of <f1>. This operation
1299# returns the form T(alpha*bil1(.,.)). Depending on the degree of the field extension, this
1300# yields either a bilinear form or a hermitian form.
1301##
1302# REMARK (ml 31/10/13) The fourth argument is a basis of L over K. This just gives
1303# extra functionality to the user, and it is used as third argument in the
1304# operation ShrinkVec.
1305# There is also a version of this field reduction operation, which does not use
1306# a basis as fourth argument. In this case the standard GAP basis is used.
1307
1308InstallMethod( HermitianFormFieldReduction,
1309	"for a hermitian form and a field",
1310	[ IsHermitianForm, IsField, IsFFE, IsBasis ],
1311	function(hf1,f2,alpha,basis)
1312	# f2 is a subfield of the basefield for the hermitian form hf1
1313	# here the basefield is always a square
1314	local f1,d1,t,d2,V2,V1,phi,b2,b2vecs,mat2,i,row,j,hf2;
1315	f1:=hf1!.basefield;
1316	d1:=Size(hf1!.matrix);
1317	t:=Dimension(AsVectorSpace(f2,f1));
1318	d2:=d1*t;
1319	V2:=f2^d2;
1320	V1:=f1^d1;
1321	b2:=Basis(V2);
1322	b2vecs:=BasisVectors(b2);
1323	mat2:=[];
1324	for i in [1..d2] do
1325		row:=[];
1326		for j in [1..d2] do
1327			Add(row,Trace(f1,f2,alpha*([ShrinkVec(f1,f2,b2vecs[i],basis),ShrinkVec(f1,f2,b2vecs[j],basis)]^hf1)));
1328		od;
1329		Add(mat2,row);
1330	od;
1331	# checking parity of t is indeed sufficient to decide, see table in manual.
1332	if IsOddInt(t) then
1333		hf2:=HermitianFormByMatrix(mat2,f2);
1334	else
1335		hf2:=BilinearFormByMatrix(mat2,f2);
1336	fi;
1337	return hf2;
1338end );
1339
1340
1341#############################################################################
1342# #O  HermitianFormFieldReduction( <hf11>, <f2>, <alpha> )
1343# The same as above, but without specified basis. In this case the canonical basis is used.
1344##
1345
1346InstallMethod( HermitianFormFieldReduction,
1347	"for a hermitian form and a field",
1348	[ IsHermitianForm, IsField, IsFFE ],
1349	function(hf1,f2,alpha)
1350	# f2 is a subfield of the basefield for the hermitian form hf1
1351	# here the basefield is always a square
1352	local basis;
1353
1354	basis:=Basis(AsVectorSpace(f2,hf1!.basefield));
1355	return HermitianFormFieldReduction(hf1,f2,alpha,basis);
1356end );
1357
1358
1359
1360##########################################################
1361#
1362# Next the embeddings for polar spaces based on the field reduction of the forms above
1363#
1364#############################################################################
1365
1366# master version for the user: handle all parameters.
1367#############################################################################
1368#O  NaturalEmbeddingByFieldReduction( <ps1>, <f2>, <alpha>, <basis>, <bool> )
1369# returns a geometry morphism, described below. If <bool> is true, then an intertwiner
1370# is computed.
1371##
1372InstallMethod (NaturalEmbeddingByFieldReduction,
1373	"for a polar space, a field, a basis, a finite field element, and a boolean",
1374	[IsClassicalPolarSpace, IsField, IsFFE, IsBasis, IsBool],
1375	function(ps1,f2,alpha,basis,computeintertwiner)
1376	# f2 must be a subfield of the basefield of the classical polar space
1377	local type1,qf1,qf2,ps2,bil1,bil2,hf1,hf2,fun,prefun,f1,
1378					map,hom,hominv,g1,gens,newgens,g2,twiner, t,q1,q2,d1,d2;
1379	type1 := PolarSpaceType(ps1);
1380	f1:=ps1!.basefield;
1381	q1 := Size(f1);
1382	q2 := Size(f2);
1383	t := LogInt(q1,q2);
1384	d1 := ps1!.dimension + 1;
1385	d2 := d1*t;
1386
1387	# 1. the polar space is of orthogonal type
1388	if type1 in ["hyperbolic","elliptic","parabolic"] then
1389		qf1:=QuadraticForm(ps1);
1390		qf2:=QuadraticFormFieldReduction(qf1,f2,alpha,basis);
1391
1392		if IsSingularForm(qf2) then
1393			Error("The field reduction does not yield a natural embedding");
1394		else ps2:=PolarSpace(qf2);
1395		fi;
1396	# 2. the polar space is of symplectic type
1397	elif type1 in ["symplectic"] then
1398		bil1:=SesquilinearForm(ps1);
1399		bil2:=BilinearFormFieldReduction(bil1,f2,alpha,basis);
1400		ps2:=PolarSpace(bil2);
1401	# 3. the polar space is of hermitian type
1402	elif type1 in ["hermitian"] then
1403		hf1:=SesquilinearForm(ps1);
1404		hf2:=HermitianFormFieldReduction(hf1,f2,alpha,basis);
1405		ps2:=PolarSpace(hf2);
1406	fi;
1407
1408	#em:=NaturalEmbeddingByFieldReduction(AmbientSpace(ps1),AmbientSpace(ps2));
1409	# this is the field reduction for projective spaces PG(r-1,q^t) -> PG(rt-1,q)
1410
1411	#fun:=function(x)
1412	#	local projfun;
1413	#	projfun:=em!.fun;
1414	#	return VectorSpaceToElement(ps2,projfun(x)!.obj);
1415	#end;
1416
1417    fun := function( subspace )
1418        local mat1,mat2;
1419        mat1 := subspace!.obj;
1420        if subspace!.type = 1 then
1421            mat1 := [subspace!.obj];
1422        fi;
1423        mat2 := BlownUpMat(basis,mat1);
1424        return VectorSpaceToElement(ps2,mat2);
1425    end;
1426
1427
1428	#prefun:=function(x)
1429	#	local projprefun;
1430	#	projprefun:=em!.prefun;
1431	#	return VectorSpaceToElement(ps1,projprefun(x)!.obj);
1432	#end;
1433
1434    # new version (2/7/14, much faster since we avoid the repeat loops and Random calls.
1435    prefun := function( subspace ) # This map is the inverse of func and returns an error, or a subspace of geom1
1436        local flag,basvecs,mat1,span,x,v,v1,i,vecs;
1437        flag:=true;
1438        if not subspace in ps2 then
1439            Error("The input is not in the range of the field reduction map!");
1440        fi;
1441        if not IsInt((Dimension(subspace)+1)/t) then
1442            flag:=false;
1443        else
1444            basvecs:=BasisVectors(basis);
1445            mat1:=[];
1446            span:=[];
1447            vecs := Unpack(UnderlyingObject(subspace));
1448            mat1 := List(vecs,x->List([1..d1],i->x{[(i-1)*t+1..i*t]}*basvecs));
1449            span := VectorSpaceToElement(AmbientSpace(ps2),BlownUpMat(basis,mat1));
1450                #the ambientspace makes sure that there is no error message here yet.
1451            if not span=subspace then
1452                flag := false;
1453            fi;
1454        fi;
1455        if flag= false then
1456            Error("The input is not in the range of the field reduction map!");
1457        fi;
1458        return VectorSpaceToElement(ps1,mat1);
1459    end;
1460
1461	map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps1), ElementsOfIncidenceStructure(ps2),
1462                                           fun, false, prefun);
1463
1464	SetIsInjective( map, true );
1465
1466	## Now creating intertwiner
1467
1468	if computeintertwiner then
1469        if (HasIsCanonicalPolarSpace( ps1 ) and IsCanonicalPolarSpace( ps1 )) or
1470            HasCollineationGroup( ps1 ) then
1471
1472            hom := function( m )
1473                local image;
1474                image := BlownUpMat(basis, m!.mat);
1475                ConvertToMatrixRepNC( image, f2 );
1476                return CollineationOfProjectiveSpace(image, f2);
1477            end;
1478            hominv := function( m )
1479                local preimage;
1480                preimage := ShrinkMat(basis, Unpack(m!.mat));
1481                ConvertToMatrixRepNC( preimage, f1 );
1482                return CollineationOfProjectiveSpace(preimage, f1);
1483            end;
1484            g1 := IsometryGroup( ps1 );
1485            gens := GeneratorsOfGroup( g1 );
1486            newgens := List(gens, hom);
1487            g2 := Group( newgens );
1488            SetSize(g2, Size(g1));
1489            twiner := GroupHomomorphismByFunction(g1, g2, hom, hominv);
1490            SetIntertwiner( map, twiner);
1491        fi;
1492	fi;
1493
1494    return map;
1495	end );
1496
1497
1498#first version: user wants a particular alpha, and basis, agrees with bool=true.
1499#############################################################################
1500#O  NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha>, <basis> )
1501# returns NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha>, <basis>, true )
1502#
1503InstallMethod (NaturalEmbeddingByFieldReduction,
1504	"for a polar space and a field, a finite field element, and a basis",
1505	[IsClassicalPolarSpace, IsField, IsFFE, IsBasis],
1506	function(ps1,f2,alpha,basis)
1507		return NaturalEmbeddingByFieldReduction(ps1,f2,alpha,basis,true);
1508	end );
1509
1510#second particular version: user wants a particular alpha, and bool, agrees with basis.
1511#############################################################################
1512#O  NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha>, <bool> )
1513# returns NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha>, <basis>, true )
1514# where <basis> is the canonical basis of BaseField(geom1) over <f2>.
1515#
1516InstallMethod (NaturalEmbeddingByFieldReduction,
1517	"for a polar space and a field, a finite field element, and a boolean",
1518	[IsClassicalPolarSpace, IsField, IsFFE, IsBool],
1519	function(ps1,f2,alpha,bool)
1520		return NaturalEmbeddingByFieldReduction(ps1,f2,alpha,Basis(AsVectorSpace(f2,BaseField(ps1))),bool);
1521	end );
1522
1523#third particular version: user wants a particular alpha, agrees with basis and bool.
1524#############################################################################
1525#O  NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha> )
1526# returns NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha>, <basis>, true )
1527# where <basis> is the canonical basis of BaseField(geom1) over <f2>.
1528#
1529InstallMethod (NaturalEmbeddingByFieldReduction,
1530	"for a polar space, a field, and a finite field element",
1531	[IsClassicalPolarSpace, IsField, IsFFE],
1532	function(ps1,f2,alpha)
1533		return NaturalEmbeddingByFieldReduction(ps1,f2,alpha,Basis(AsVectorSpace(f2,BaseField(ps1))),true);
1534	end );
1535
1536# fourth particular version: user agrees but wants to control intertwiner
1537#############################################################################
1538#O  NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <bool> )
1539# returns NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <one>, <basis>, <bool> )
1540# where <basis> is the canonical basis of BaseField(geom1) over <f2>, and
1541# <one> is One(BaseField(geom1)). This might be usefull if you agree with the defaults, but doesn't
1542# want the intertwiner.
1543#
1544InstallMethod (NaturalEmbeddingByFieldReduction,
1545	"for a polar space, a field, and a boolean",
1546	[IsClassicalPolarSpace, IsField, IsBool],
1547	function(ps1,f2,bool)
1548		return NaturalEmbeddingByFieldReduction(ps1,f2,One(f2),Basis(AsVectorSpace(f2,BaseField(ps1))),bool);
1549	end );
1550
1551# fifth particular version: user agrees with everything
1552#############################################################################
1553#O  NaturalEmbeddingByFieldReduction( <geom1>, <f2> )
1554# returns NaturalEmbeddingByFieldReduction( <geom1>, <f2>, <alpha>, <basis>, true )
1555# where <basis> is the canonical basis of BaseField(geom1) over <f2>, and
1556# <alpha> is One(f2).
1557InstallMethod (NaturalEmbeddingByFieldReduction,
1558	"for a polar space and a field",
1559	[IsClassicalPolarSpace, IsField],
1560	function(ps1,f2)
1561		return NaturalEmbeddingByFieldReduction(ps1,f2,One(f2),Basis(AsVectorSpace(f2,BaseField(ps1))),true);
1562	end );
1563
1564# CHECKED 28/09/11 jdb
1565#############################################################################
1566#O  NaturalEmbeddingByFieldReduction( <geom1>, <geom2> )
1567# returns NaturalEmbeddingByFieldReduction(<geom1>, <geom2> )
1568##
1569InstallMethod( NaturalEmbeddingByFieldReduction,
1570	"for two polar spaces",
1571	[ IsClassicalPolarSpace, IsClassicalPolarSpace ],
1572	function( geom1, geom2 )
1573		return NaturalEmbeddingByFieldReduction( geom1, geom2, true );
1574	end );
1575
1576# ml 31/10/2013
1577# changed with new prefun 2/7/14 jdb
1578#####################################################################################
1579# O  NaturalEmbeddingByFieldReduction
1580# This NaturalEmbeddingByFieldReduction takes two polar spaces and returns an embedding
1581# obtained by field reduction if such an embedding exists, otherwise it returns an error.
1582##
1583InstallMethod (NaturalEmbeddingByFieldReduction,
1584	"for two classical polar spaces",
1585	[IsClassicalPolarSpace, IsClassicalPolarSpace, IsBool],
1586	function(ps1, ps2, computeintertwiner)
1587
1588	#####################################################################
1589	#  List of possible embeddings of ps1 in PG(r-1, q^t) -> ps2 in PG(rt-1, q)
1590	#
1591	#  Hermitian
1592	#  	H -> H (t odd)
1593	#  	H -> W (t even)
1594	#  	H -> Q+ (t even, r even)
1595	#  	H -> Q- (t even, r odd)
1596	#
1597	#  Symplectic
1598	#  	W -> W (always)
1599	#
1600	#  Orthogonal
1601	# r even
1602	#	Q+ -> Q+ (always)
1603	#	Q- -> Q- (always)
1604	# r odd
1605	#	Q -> Q (t odd, q odd)
1606	#   Q -> Q+ (t even, q odd)
1607	#   Q -> Q- (t even, q odd)
1608	#####################################################################
1609
1610    local t, r, type1, type2, form1, f1, f2, newps, sigma, map,
1611		  gamma, bil1, q, n, alpha, basis, is_square, iso, form2, fun, prefun,
1612          c1, c2, cps2, cps2inv, d1, hom, hominv, g1, g2, gens, newgens, twiner;
1613
1614	type1 := PolarSpaceType(ps1);
1615	type2 := PolarSpaceType(ps2);
1616	if type1 in ["hyperbolic", "parabolic", "elliptic"] then
1617	   form1 := QuadraticForm(ps1);
1618	else
1619	   form1 := SesquilinearForm(ps1);
1620	fi;
1621    d1 := ps1!.dimension+1;
1622	f1 := ps1!.basefield;
1623	f2 := ps2!.basefield;
1624	q := Size(f2);
1625
1626	if not IsSubset(f1,f2) then
1627	   Error("Fields are incompatible");
1628	fi;
1629	t := Log(Size(f1),q);
1630	r := ProjectiveDimension(ps1)+1;
1631	basis := CanonicalBasis(AsVectorSpace(f2,f1));
1632    is_square := function(x, f) return IsZero(x) or IsEvenInt(LogFFE(x,PrimitiveRoot(f))); end;
1633
1634	if IsSesquilinearForm(form1) then
1635       	if type1 = "hermitian" and type2 = "hermitian" and IsOddInt(t) then
1636			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1637				# need alpha to be fixed by sigma:
1638			alpha := One(f1);
1639			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1640			form2 := HermitianFormFieldReduction(form1,f2,alpha,basis);
1641	   	elif type1 = "hermitian" and type2 = "symplectic" and IsEvenInt(t) then
1642			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1643			if IsEvenInt(q) then
1644				# need alpha to be fixed by sigma:
1645				alpha := One(f1);
1646			else
1647				# need sigma(alpha)=-alpha
1648				sigma := Sqrt(Size(f1));
1649				alpha := First(f1, x->not IsZero(x) and x^sigma = -x);
1650			fi;
1651			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1652            form2 := HermitianFormFieldReduction(form1,f2,alpha,basis);
1653
1654	   	elif type1 = "hermitian" and type2 = "hyperbolic" and IsEvenInt(t) and IsEvenInt(r)
1655				and IsOddInt(q) then
1656			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1657			alpha := One(f1);
1658			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1659            form2 := HermitianFormFieldReduction(form1,f2,alpha,basis);
1660
1661	   	elif type1 = "hermitian" and type2 = "elliptic" and IsEvenInt(t) and IsOddInt(r)
1662				and IsOddInt(q) then
1663			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1664			alpha := One(f1);
1665			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1666            form2 := HermitianFormFieldReduction(form1,f2,alpha,basis);
1667
1668	   	elif type1 = "symplectic" and type2 = "symplectic" then
1669			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1670			alpha := One(f1);
1671			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1672            form2 := BilinearFormFieldReduction(form1,f2,alpha,basis);
1673        else
1674		 	Error("These polar spaces are not suitable for field reduction.");
1675		fi;
1676	else 	# form1 is a quadratic form
1677		if type1 = "hyperbolic" and type2 = "hyperbolic" then
1678			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1679			alpha := One(f1);
1680			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1681            form2 := QuadraticFormFieldReduction(form1,f2,alpha,basis);
1682		elif type1 = "elliptic" and type2 = "elliptic" then
1683			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1684			alpha := One(f1);
1685			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1686            form2 := QuadraticFormFieldReduction(form1,f2,alpha,basis);
1687		elif type1 = "parabolic" and type2 = "parabolic" and IsOddInt(t) and IsOddInt(q) then
1688			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction");
1689			alpha := One(f1);
1690			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1691            form2 := QuadraticFormFieldReduction(form1,f2,alpha,basis);
1692
1693		# If ps1 is parabolic and ps2 is hyperbolic or elliptic,
1694		# then the choice of alpha that we will use for the embedding
1695		# depends on the isometry type of ps1. The isometry type is determined by
1696		# gamma being a square or not, where gamma is
1697		# (-1)^(r-1)/2 times the determinant of the bilinear form, divided by two.
1698		# The conditions we use here are taken from [Lavrauw - Van de Voorde: "Field
1699		# reduction and linear sets in finite geometry", preprint], they are
1700		# somewhat easier to work with than the conditions in Gill's paper.
1701
1702		elif type1 = "parabolic" and type2 = "hyperbolic" and IsEvenInt(t) and IsOddInt(q) then
1703			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction: Parabolic -> Hyperbolic");
1704			bil1:=AssociatedBilinearForm(form1); # important to use this instead of
1705			# BilinearFormByQuadraticForm (see documentation of the Forms package).
1706			gamma:=((-1)^((r-1)/2)) * Determinant(bil1!.matrix)/2;
1707			if q^(t/2) mod 4 = 1 then # the product of alpha and gamma must be a nonsquare
1708 				alpha := First(f1, a -> not IsZero(a) and not is_square( a*gamma, f1 ) );
1709			else # the product of alpha and gamma must be a square
1710				alpha := First(f1, a -> not IsZero(a) and is_square( a*gamma, f1 ) );
1711			fi;
1712			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1713            form2 := QuadraticFormFieldReduction(form1,f2,alpha,basis);
1714
1715		elif type1 = "parabolic" and type2 = "elliptic" and IsEvenInt(t) and IsOddInt(q) then
1716			Info(InfoFinInG, 1, "These polar spaces are suitable for field reduction: Parabolic -> Elliptic");
1717			bil1:=AssociatedBilinearForm(form1); # important to use this instead of
1718			# BilinearFormByQuadraticForm (see documentation of the Forms package).
1719			gamma:=((-1)^((r-1)/2)) * Determinant(bil1!.matrix)/2;
1720			if q^(t/2) mod 4 = 1 then # the product of alpha and gamma must be a square
1721 				alpha := First(f1, a -> not IsZero(a) and is_square( a*gamma, f1 ) );
1722			else # the product of alpha and gamma must be a nonsquare
1723				alpha := First(f1, a -> not IsZero(a) and not is_square( a*gamma, f1 ) );
1724			fi;
1725			#map := NaturalEmbeddingByFieldReduction( ps1, f2, alpha, basis );
1726            form2 := QuadraticFormFieldReduction(form1,f2,alpha,basis);
1727
1728		else
1729		 	Error("These polar spaces are not suitable for field reduction.");
1730     	fi;
1731	fi;
1732
1733	c1 := BaseChangeToCanonical( form2 );
1734    if not IsCanonicalPolarSpace(ps2) then
1735        if type2 in ["parabolic", "hyperbolic", "elliptic"] then
1736            c2 := BaseChangeToCanonical(QuadraticForm(ps2));
1737        else
1738            c2 := BaseChangeToCanonical(SesquilinearForm(ps2));
1739        fi;
1740        cps2 := c2^-1 * c1;
1741    else
1742        cps2 := c1;
1743    fi;
1744    cps2inv := cps2^-1;
1745
1746    fun := function( subspace )
1747        local mat1,mat2;
1748        mat1 := subspace!.obj;
1749        if subspace!.type = 1 then
1750            mat1 := [subspace!.obj];
1751        fi;
1752        mat2 := BlownUpMat(basis,mat1);
1753        return VectorSpaceToElement(ps2,mat2*cps2inv);
1754    end;
1755
1756    #this version of prefun is new (2/7/14).
1757
1758    prefun := function( subspace ) # This map is the inverse of func and returns an error, or a subspace of geom1
1759        local flag,basvecs,mat1,span,x,v,v1,i,vecs;
1760        flag:=true;
1761        if not subspace in ps2 then
1762            Error("The input is not in the range of the field reduction map!");
1763        fi;
1764        if not IsInt((Dimension(subspace)+1)/t) then
1765            flag:=false;
1766        else
1767            basvecs:=BasisVectors(basis);
1768            vecs := Unpack(UnderlyingObject(subspace))*cps2;
1769            mat1 := List(vecs,x->List([1..d1],i->x{[(i-1)*t+1..i*t]}*basvecs));
1770            span := VectorSpaceToElement(AmbientSpace(ps2),BlownUpMat(basis,mat1)*cps2inv); #the ambientspace makes sure that there is no error message here yet.
1771            if not span = subspace then
1772                flag := false;
1773            fi;
1774        fi;
1775        if flag = false then
1776            Error("The input is not in the range of the field reduction map!");
1777        fi;
1778        return VectorSpaceToElement(ps1,mat1);
1779    end;
1780
1781    #iso := IsomorphismPolarSpacesNC(AmbientGeometry(Range(map)), ps2);
1782	#morphism := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps1),
1783    #                                   ElementsOfIncidenceStructure(ps2),
1784    #                                    x -> iso!.fun(map!.fun(x)), false,  x -> map!.prefun(iso!.prefun(x)) );
1785
1786	map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps1),
1787                                         ElementsOfIncidenceStructure(ps2),
1788                                            fun, false, prefun);
1789	SetIsInjective( map, true );
1790
1791    if (HasIsCanonicalPolarSpace( ps1 ) and IsCanonicalPolarSpace( ps1 )) or
1792            HasCollineationGroup( ps1 ) then
1793        if computeintertwiner then
1794            hom := function( m )
1795                local image;
1796                image := BlownUpMat(basis, m!.mat);
1797                ConvertToMatrixRepNC( image, f2 );
1798                return CollineationOfProjectiveSpace(cps2 * image * cps2inv, f2);
1799            end;
1800            hominv := function( m )
1801                local preimage;
1802                preimage := ShrinkMat(basis, cps2inv * Unpack(m!.mat) * cps2);
1803                ConvertToMatrixRepNC( preimage, f1 );
1804                return CollineationOfProjectiveSpace(preimage, f1);
1805            end;
1806            g1 := IsometryGroup( ps1 );
1807            gens := GeneratorsOfGroup( g1 );
1808            newgens := List(gens, hom);
1809            g2 := Group( newgens );
1810            SetSize(g2, Size(g1));
1811            twiner := GroupHomomorphismByFunction(g1, g2, hom, hominv);
1812            SetIntertwiner( map, twiner);
1813        fi;
1814    fi;
1815
1816	SetIntertwiner(map, hom );
1817
1818	return map;
1819end );
1820
1821# added 2/7/14 jdb
1822#####################################################################################
1823# O  NaturalEmbeddingByFieldReduction
1824# This NaturalEmbeddingByFieldReduction takes two polar spaces and returns an embedding
1825# obtained by field reduction if such an embedding exists, otherwise it returns an error.
1826##
1827InstallMethod (NaturalEmbeddingByFieldReduction,
1828	"for two classical polar spaces",
1829	[IsClassicalPolarSpace, IsClassicalPolarSpace],
1830	function(ps1, ps2)
1831        return NaturalEmbeddingByFieldReduction(ps1,ps2,true);
1832    end );
1833
1834
1835#############################################################################
1836#
1837#
1838# Embeddings by SUBFIELDS
1839#
1840#
1841#############################################################################
1842
1843
1844# CHECKED 11/06/14 jdb
1845# found another cvec/cmat bug thanks to our testuser Geertrui!
1846#############################################################################
1847#O  NaturalEmbeddingBySubfield( <geom1>, <geom2> )
1848# returns the embedding of <geom1> in <geom2>, two projective spaces of the
1849# same dimension, over the fields K and L respectively, where L is an extension
1850# of K.
1851##
1852InstallMethod( NaturalEmbeddingBySubfield,
1853	"for two projective spaces",
1854	[ IsProjectiveSpace, IsProjectiveSpace ],
1855	function( geom1, geom2 )
1856		local map, f1, f2, func, prefun, g1, g2,
1857          gens1, gens2, hom, twinerfunc, twinerprefun;
1858		f1 := geom1!.basefield;
1859		f2 := geom2!.basefield;
1860		if geom1!.dimension  <> geom2!.dimension or not IsSubset(f2,f1) then
1861			Error("Dimensions and/or field sizes are incompatible"); return;
1862		fi;
1863
1864        func :=  x -> VectorSpaceToElement(geom2, Unpack(x!.obj)); #here an unpack is needed to avoid field incompatibilites.
1865
1866        prefun := function( x )
1867            local xmat;
1868			if not ForAll( Flat(x!.obj), i -> i in f1 ) then
1869				Error("Element does not have all of its coordinates in ", f1);
1870			fi;
1871            xmat := Unpack(x!.obj);
1872            if x!.type = 1 then
1873                ConvertToVectorRep(xmat);
1874            else
1875                ConvertToMatrixRep(xmat);
1876            fi;
1877			return VectorSpaceToElement(geom1, xmat ); # the same here.
1878		end;
1879
1880        map := GeometryMorphismByFunction( ElementsOfIncidenceStructure(geom1),
1881                                       ElementsOfIncidenceStructure(geom2),
1882                                       func, false, prefun );
1883		SetIsInjective( map, true );
1884
1885    ## Intertwiner...
1886
1887		twinerfunc := function( x )
1888            local xmat;
1889            if not IsOne(x!.frob) then
1890				Info(InfoFinInG, 1, "<el> is not in the source of the intertwiner");
1891                return fail;
1892            fi;
1893            xmat := x!.mat;
1894            return CollineationOfProjectiveSpace( Unpack(x!.mat), f2 ); #here was an unpack needed!
1895        end;
1896
1897		twinerprefun := function( y )
1898            local ymat,q;
1899            if not IsOne(y!.frob) then
1900				Info(InfoFinInG, 1, "<el> is not in the range of the intertwiner");
1901                return fail;
1902            fi;
1903            ymat := Unpack(y!.mat); #here too!
1904            q := ConvertToMatrixRep(ymat);
1905            if not q = Size(f1) then
1906   				Info(InfoFinInG, 1, "<el> is not in the range of the intertwiner");
1907                return fail;
1908            fi;
1909            return CollineationOfProjectiveSpace(ymat,f1);
1910        end;
1911
1912        g1 := ProjectivityGroup( geom1 );
1913		gens1 := GeneratorsOfGroup( g1 );
1914		gens2 := List(gens1, twinerfunc );
1915		g2 := Group(gens2);
1916		SetSize(g2, Size(g1));
1917		hom := GroupHomomorphismByFunction(g1, g2, twinerfunc, twinerprefun);
1918		SetIntertwiner(map, hom);
1919		return map;
1920	end );
1921
1922# CHECKED 25/7/14 jdb. With new (and correct now) prefun.
1923# cmat changes 21/3/14 and 24/3/2014, after a nice cultural weekend :-)
1924#############################################################################
1925#O  NaturalEmbeddingBySubfield( <geom1>, <geom2>, <bool> )
1926# returns the embedding of <geom1> in <geom2>, two polar spaces of the
1927# same dimension, over the fields K and L respectively, where L is an extension
1928# of K. An Intertwiner is computed if <bool> is true.
1929# The prefun principle is taken from NaturalDualityHermitian.
1930##
1931InstallMethod( NaturalEmbeddingBySubfield,
1932	"for two polar spaces and a boolean",
1933	[ IsClassicalPolarSpace, IsClassicalPolarSpace, IsBool ],
1934	function( geom1, geom2, computeintertwiner )
1935		local map, f1, f2, q1, q2, gamma, func, prefun, g1, g2, gens1, gens2, f,
1936          r, type1, type2, iso, gram, newgram, form, ps, hom, twinerfunc, twinerprefun,
1937		  change, invchange, basis, d, n, bmat, one, i, c1, c2, form2;
1938
1939    #
1940    #  Check Kleidman and Liebeck Table 4.5.A
1941    #  There, the conditions on the polarity types can be found.
1942    #
1943    #  All possible cases:
1944    #  W -> W
1945    #  W -> H (quadratic)
1946    #  O^eps -> H (q odd, quadratic)
1947    #  H -> H (odd degree)
1948    #  O^eps -> O^eps' (eps = eps'^r)
1949    #
1950
1951		f1 := geom1!.basefield; q1 := Size(f1);
1952		f2 := geom2!.basefield; q2 := Size(f2);
1953		type1 := PolarSpaceType(geom1);
1954		type2 := PolarSpaceType(geom2);
1955		form2 := SesquilinearForm( geom2 ); #will be changed in last case only
1956		if geom1!.dimension = geom2!.dimension and IsSubset(f2, f1) then
1957			r := LogInt(q2, q1);
1958			gram := GramMatrix( SesquilinearForm(geom1) );
1959			if [type1, type2] = ["symplectic", "hermitian" ] and r = 2 then
1960				gamma := First( f2, t -> not IsZero(t) and IsZero(t^q1+t));
1961				newgram := gamma * gram;
1962				form := HermitianFormByMatrix(newgram, f2);
1963			elif [type1, type2] = ["symplectic", "symplectic"] then
1964				form := BilinearFormByMatrix(gram, f2);
1965			elif [type1, type2] = ["hermitian", "hermitian"] and IsOddInt(r) then
1966				form := HermitianFormByMatrix(gram, f2);
1967			elif type1 in ["elliptic", "parabolic", "hyperbolic"] and
1968				type2 = "hermitian" and r=2 and IsOddInt(q2) then
1969				form := HermitianFormByMatrix(gram, f2);
1970			elif (IsOddInt(r) and type2 in ["elliptic", "parabolic", "hyperbolic"] and
1971				type1 = type2) or (IsEvenInt(r) and [type1, type2] in [["elliptic","hyperbolic"],
1972				["hyperbolic","hyperbolic"], ["parabolic","parabolic"]]) then
1973				gram := GramMatrix( QuadraticForm(geom1) );
1974				form := QuadraticFormByMatrix(gram, f2);
1975				form2 := QuadraticForm(geom2);
1976			else
1977				Error("Not possible for these geometries\n");
1978			fi;
1979		else
1980			Error("Dimensions and/or field sizes are incompatible"); return;
1981		fi;
1982
1983		c1 := BaseChangeToCanonical( form );
1984		if not IsCanonicalPolarSpace( geom2 ) then
1985			c2 := BaseChangeToCanonical( form2 );
1986			change := c2^-1 * c1;
1987		else
1988			change := c1;
1989		fi;
1990		invchange := change^-1;
1991
1992		d := geom1!.dimension+1;
1993		basis := Basis(AsVectorSpace(f1,f2));
1994		n := Length(basis);
1995		bmat := NullMat(d,d*n,f1);
1996		one := One(f1);
1997		for i in [1..d] do
1998			bmat[i][1+(i-1)*n] := one;
1999		od;
2000
2001		func := function( el )
2002			return VectorSpaceToElement(geom2,Unpack(el!.obj) * invchange);
2003		end;
2004
2005		prefun := function( el )
2006			local vec,nvec,list,bgen;
2007			vec := Unpack(el!.obj) * change;
2008			if el!.type = 1 then
2009				nvec := vec / First(vec,x->not IsZero(x));
2010				if not ForAll( nvec, i -> i in f1 ) then
2011					Error("Element is not in the range of the geometry morphism");
2012				fi;
2013				ConvertToVectorRepNC(nvec,f1); #necessary, also if appearantly vec is already over f1.
2014			else
2015				bgen := BlownUpMat(basis,vec);
2016				list := SumIntersectionMat(bgen, bmat)[2]; #hard coded Meet operation :-)
2017				nvec := List(list,x->ShrinkVec(f2,f1,x));
2018				if not (IsMatrix(nvec) and Length(nvec) = el!.type) then
2019					#return fail;
2020					Error("Element is not in the range of the geometry morphism");
2021				fi;
2022				ConvertToMatrixRepNC(nvec,f1);
2023			fi;
2024			return VectorSpaceToElement(geom1,nvec);
2025		end;
2026
2027		map := GeometryMorphismByFunction( ElementsOfIncidenceStructure(geom1),
2028                                       ElementsOfIncidenceStructure(geom2),
2029                                       func, false, prefun );
2030		SetIsInjective( map, true );
2031
2032   		twinerfunc := function( x )
2033            local xmat;
2034            if not IsOne(x!.frob) then
2035				Info(InfoFinInG, 1, "<el> is not in the source of the intertwiner");
2036                return fail;
2037            fi;
2038            xmat := x!.mat;
2039            return CollineationOfProjectiveSpace( change * Unpack(x!.mat) * invchange, f2 ); #here was an unpack needed!
2040        end;
2041
2042		twinerprefun := function( y )
2043            local ymat,q;
2044            if not IsOne(y!.frob) then
2045				Info(InfoFinInG, 1, "<el> is not in the range of the intertwiner");
2046                return fail;
2047            fi;
2048            ymat := invchange * Unpack(y!.mat) * change; #here too!
2049            ymat := ymat/First(ymat[1],x->not IsZero(x));
2050			if not ForAll( Flat(ymat), i -> i in f1 ) then
2051				Info(InfoFinInG, 1, "<el> is not in the range of the intertwiner");
2052				return fail;
2053			else
2054				ConvertToMatrixRepNC(ymat,f1);
2055                return CollineationOfProjectiveSpace(ymat,f1);
2056            fi;
2057        end;
2058
2059        if HasCollineationGroup( geom1 ) then
2060			g1 := SimilarityGroup( geom1 );
2061			gens1 := GeneratorsOfGroup( g1 );
2062			gens2 := List(gens1, twinerfunc );
2063			g2 := Group(gens2);
2064			SetSize(g2, Size(g1));
2065			hom := GroupHomomorphismByFunction(g1, g2, twinerfunc, twinerprefun);
2066			SetIntertwiner(map, hom);
2067		else
2068			Info(InfoFinInG, 1, "No intertwiner computed. <geom1> must have a collineation group computed");
2069		fi;
2070
2071		return map;
2072
2073	end );
2074
2075# CHECKED 28/09/11 jdb
2076#############################################################################
2077#O  NaturalEmbeddingBySubfield( <geom1>, <geom2> )
2078# returns NaturalEmbeddingBySubfield( <geom1>, <geom2>, true )
2079##
2080InstallMethod( NaturalEmbeddingBySubfield,
2081	"for two polar spaces",
2082	[ IsClassicalPolarSpace, IsClassicalPolarSpace ],
2083	function( geom1, geom2 )
2084		return NaturalEmbeddingBySubfield( geom1, geom2, true );
2085	end );
2086
2087
2088#############################################################################
2089#
2090# PROJECTIONS
2091#
2092#############################################################################
2093
2094# CHECKED 28/09/11 jdb
2095#############################################################################
2096#O  NaturalProjectionBySubspace( <ps>, <v> )
2097# returns the morphism from the projective space to the quotient space of the
2098# subspace <v>. It is checked if <v> is a subspace of <ps>.
2099##
2100InstallMethod( NaturalProjectionBySubspace,
2101	"for a projective space and a subspace of the projective space",
2102	[ IsProjectiveSpace, IsSubspaceOfProjectiveSpace ],
2103	function(ps, v)
2104
2105  ## map from geometry of elements incident with v
2106  ## to the projective space of lesser dimension
2107
2108    local psdim, b, vdim, vs, sub, func, pre,
2109          Wvectors, mb, compl, gen, bas, img,
2110          canbas, zero, basimgs, ps2, map, hom, f;
2111    if not v in ps then
2112       Error("<v> is not an element of <ps>");
2113    fi;
2114    psdim := ps!.dimension;
2115    f := ps!.basefield;
2116    b := Unpack(v!.obj); #cmat unpack necessary for infra.
2117    vdim := v!.type;
2118    if vdim = 1 then b:=[b]; fi;
2119    vs := ps!.vectorspace;
2120    sub := Subspace(vs, b);
2121
2122    ## if v has dimension j, then the new geometry
2123    ## has dimension psdim - vdim
2124
2125    ps2 := ProjectiveSpace( psdim - vdim, f );
2126
2127      ## This code was adapted from Thomas Breuer's
2128      ## "NaturalHomomorphismBySubspace" code
2129
2130    Wvectors:= b;
2131    mb:= MutableBasis( f, Wvectors );
2132    compl:= [];
2133    for gen in BasisVectors( Basis( vs ) ) do
2134        if not IsContainedInSpan( mb, gen ) then
2135          Add( compl, gen );
2136          CloseMutableBasis( mb, gen );
2137        fi;
2138    od;
2139    bas := BasisNC( vs, Concatenation( Wvectors, compl ) );
2140
2141        # Compute the linear mapping by images.
2142    img:= FullRowModule( f, Length( compl ) );
2143    canbas:= CanonicalBasis( img );
2144    zero:= Zero( img );
2145        # Matrix of the linear transformation we want
2146    basimgs:= Concatenation( ListWithIdenticalEntries( Size(Wvectors), zero ),
2147                             BasisVectors( canbas ) );
2148
2149    func := function( x )
2150		local y;
2151        if not v in x then
2152			Error("Subspace is not incident with the subspace of projection");
2153		fi;
2154        y := List(Unpack(Unwrap(x)),i-> Coefficients(bas,i))*basimgs;  #was x^_, now UnWrap(x), cmat unpack
2155        if not IsEmpty(y) then
2156           ## Note: TriangulizeMat does not return a matrix of full
2157           ##       rank, whereas SemiEchelonMat does!
2158     		y := SemiEchelonMat(y)!.vectors;  ## JB: 3/11/2012. Moved this line from the "else" to here so it also applies to the first case.
2159
2160			if x!.type - vdim = 1 then
2161				y := y[1];
2162				#ConvertToVectorRep(y, f);
2163			fi;
2164			#else
2165			#	ConvertToMatrixRepNC(y, f);
2166			#fi;
2167        fi;
2168		if not IsEmpty(y) then
2169			#return Wrap(ps2, x!.type - vdim, y);
2170            return VectorSpaceToElement(ps2,y);
2171			else
2172				return EmptySubspace(ps2);
2173			fi;
2174		end;
2175		pre := function( y )
2176			local x;
2177            x := Unpack(y!.obj);  #cmat unpack
2178            if y!.type = 1 then x := [x]; fi;
2179            x :=  Concatenation(x * compl, b);
2180            #ConvertToMatrixRepNC(x, f);
2181            return VectorSpaceToElement(ps, x);
2182		end;
2183		map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps),
2184                                      ElementsOfIncidenceStructure(ps2), func, pre );
2185		return map;
2186	end );
2187
2188# CHECKED 28/09/11 jdb
2189#############################################################################
2190#O  NaturalProjectionBySubspaceNC( <ps>, <v> )
2191## This operation is just like its namesake except that it
2192## has no checks
2193##
2194InstallMethod( NaturalProjectionBySubspaceNC,
2195	"for a projective space and a singular subspace",
2196	[ IsProjectiveSpace, IsSubspaceOfProjectiveSpace ],
2197	function(ps, v)
2198
2199  ## map from geometry of elements incident with v
2200  ## to the projective space of lesser dimension
2201
2202    local psdim, b, vdim, vs, sub, func, pre,
2203          Wvectors, mb, compl, gen, bas, img,
2204          canbas, zero, basimgs, ps2, map, hom, f;
2205
2206    psdim := ps!.dimension;
2207    f := ps!.basefield;
2208    b := v!.obj;
2209    vdim := v!.type;
2210    if vdim = 1 then b:=[b]; fi;
2211    vs := ps!.vectorspace;
2212    sub := Subspace(vs, b);
2213
2214    ## if v has dimension j, then the new geometry
2215    ## has dimension psdim - vdim
2216
2217    ps2 := ProjectiveSpace( psdim - vdim, f );
2218
2219      ## This code was adapted from Thomas Breuer's
2220      ## "NaturalHomomorphismBySubspace" code
2221
2222    Wvectors:= b;
2223    mb:= MutableBasis( f, Wvectors );
2224    compl:= [];
2225    for gen in BasisVectors( Basis( vs ) ) do
2226        if not IsContainedInSpan( mb, gen ) then
2227          Add( compl, gen );
2228          CloseMutableBasis( mb, gen );
2229        fi;
2230    od;
2231    bas := BasisNC( vs, Concatenation( Wvectors, compl ) );
2232
2233        # Compute the linear mapping by images.
2234    img:= FullRowModule( f, Length( compl ) );
2235    canbas:= CanonicalBasis( img );
2236    zero:= Zero( img );
2237        # Matrix of the linear transformation we want
2238    basimgs:= Concatenation( ListWithIdenticalEntries( Size(Wvectors), zero ),
2239                             BasisVectors( canbas ) );
2240
2241    func := function( x )
2242              local y;
2243              y := List(x!.obj,i-> Coefficients(bas,i))*basimgs;
2244              y := SemiEchelonMat(y)!.vectors;
2245              if x!.type - vdim = 1 then
2246                 y := y[1];
2247                 ConvertToVectorRep(y, f);
2248              else
2249                 ConvertToMatrixRepNC(y, f);
2250              fi;
2251			  if not IsEmpty(y) then
2252			 	 return Wrap(ps2, x!.type - vdim, y);
2253	          else
2254			     return EmptySubspace(ps2);
2255			  fi;
2256			end;
2257
2258    pre := function( y )
2259              local x;
2260              x := y!.obj;
2261              if y!.type = 1 then x := [x]; fi;
2262              x :=  Concatenation(x * compl, b);
2263              ConvertToMatrixRepNC(x, f);
2264              return VectorSpaceToElement(ps, x);
2265           end;
2266
2267    map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps),
2268                                      ElementsOfIncidenceStructure(ps2), func, pre );
2269    return map;
2270  end );
2271
2272# CHECKED 28/09/11 jdb
2273#############################################################################
2274#O  QUO( <ps>, <v> )
2275# returns the quotient space of the subspace <v> of the projective space <ps>
2276# it is checked if <v> is a subspace of <ps>
2277##
2278InstallOtherMethod(QUO,
2279	"for a projective space and a subspace",
2280	[ IsProjectiveSpace and IsProjectiveSpaceRep, IsSubspaceOfProjectiveSpace],
2281	function( ps, v )
2282		if not v in ps then
2283			Error( "Subspace does not belong to the projective space" );
2284		fi;
2285		return Range( NaturalProjectionBySubspace( ps, v ) )!.geometry;
2286	end );
2287
2288# CHECKED 28/09/11 jdb
2289#cmat change 21/3/14
2290#############################################################################
2291#O  NaturalProjectionBySubspace( <ps>, <v> )
2292# returns the morphism from the polar space to the quotient space of the
2293# subspace <v>. It is checked if <v> is a subspace of <ps>.
2294##
2295InstallMethod( NaturalProjectionBySubspace,
2296    "for a polar space and a subspace",
2297    [ IsClassicalPolarSpace, IsSubspaceOfClassicalPolarSpace ],
2298	function(ps, v)
2299
2300  ## map from geometry of elements incident with v
2301  ## to the polar space of the same type but of lesser dimension
2302    local pstype, psdim, b, vdim, vs, func, pre,
2303          Wvectors, mb, compl, gen, bas, img, canbas, zero, basimgs,
2304          ps2, map, hom, a, m, newform, f, perp;
2305    if not v in ps then
2306		Error("<v> is not an element of <ps>");
2307    fi;
2308    pstype := PolarSpaceType(ps);
2309    psdim := ps!.dimension;
2310    f := ps!.basefield;
2311    b := Unpack(v!.obj); #cmat unpack necessary for infra.
2312    vdim := v!.type;
2313    if vdim = 1 then b:=[b]; fi;
2314    perp := PolarMap(ps);
2315    vs := VectorSpace(f, Unpack(perp(v)!.obj)); #PolarMap maps to elements, containing a cmat.
2316
2317    ## if v has dimension j, then the new geometry
2318    ## has dimension psdim - 2*vardim
2319
2320    Wvectors:= b;
2321    mb:= MutableBasis( f, Wvectors );
2322    compl:= [];
2323    for gen in BasisVectors( Basis( vs ) ) do
2324        if not IsContainedInSpan( mb, gen ) then
2325          Add( compl, gen );
2326          CloseMutableBasis( mb, gen );
2327        fi;
2328    od;
2329    bas := BasisNC( vs, Concatenation( Wvectors, compl ) );
2330
2331    # Compute the linear mapping by images.
2332    img:= FullRowModule( f, Length( compl ) );
2333    canbas:= CanonicalBasis( img );
2334    zero:= Zero( img );
2335    # Matrix of the linear transformation we want
2336    basimgs:= Concatenation( List( Wvectors, v -> zero ),
2337                             BasisVectors( canbas ) );
2338    #ConvertToMatrixRep(basimgs, f); #useles now.
2339    a := compl;
2340
2341    if HasQuadraticForm( ps ) then
2342       m := QuadraticForm(ps)!.matrix;
2343       newform := QuadraticFormByMatrix(a * m * TransposedMat(a), f);
2344    else
2345       m := SesquilinearForm(ps)!.matrix;
2346       if pstype = "hermitian" then
2347          #newform := HermitianFormByMatrix(a^CompanionAutomorphism(ps) * m * TransposedMat(a), f); #finally found the bug, jdb gets thirrrrrrsty now.
2348		  newform := HermitianFormByMatrix(a * m * TransposedMat(a^CompanionAutomorphism(ps)), f);
2349
2350       else
2351          newform := BilinearFormByMatrix(a * m * TransposedMat(a), f);
2352       fi;
2353    fi;
2354    ps2 := PolarSpace( newform );
2355
2356
2357    func := function( x )
2358              local y;
2359              if not v in x then
2360                 Error("Subspace is not incident with the subspace of projection");
2361              fi;
2362              if not x in ps then
2363                 Error("Subspace is not an element of the polar space");
2364              fi;
2365              if v = x then return
2366                 EmptySubspace(ps2);
2367              else
2368                 y := List(Unpack(x!.obj),i-> Coefficients(bas,i))*basimgs;  #cmat unpack here
2369                 y := SemiEchelonMat(y)!.vectors;
2370                 if x!.type - vdim = 1 then
2371                    y := y[1];
2372                    #ConvertToVectorRep(y, f);
2373                 #else
2374                 #   ConvertToMatrixRepNC(y, f);
2375                 fi;
2376                 return VectorSpaceToElement(ps2, y);
2377              fi;
2378           end;
2379
2380    pre := function( y )
2381              local x;
2382              if not y in ps2 then
2383                 Error("Subspace is not an element of the polar space");
2384              fi;
2385              x := Unpack(y!.obj); #cmat unpack here.
2386              if y!.type = 1 then x := [x]; fi;
2387              x := Concatenation(x * compl, StructuralCopy( b ));
2388              #ConvertToMatrixRepNC(x, f); #useless now.
2389              return VectorSpaceToElement(ps, x);
2390            end;
2391    map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps),
2392                                      ElementsOfIncidenceStructure(ps2), func, false, pre );
2393    return map;
2394  end );
2395
2396# CHECKED 28/09/11 jdb
2397#############################################################################
2398#O  NaturalProjectionBySubspaceNC( <ps>, <v> )
2399## This operation is just like its namesake except that it
2400## has no checks
2401##
2402InstallMethod( NaturalProjectionBySubspaceNC,
2403      "for a polar space and a singular subspace",
2404      [ IsClassicalPolarSpace, IsSubspaceOfClassicalPolarSpace ],
2405  function(ps, v)
2406
2407  ## map from geometry of elements incident with v
2408  ## to the polar space of the same type but of lesser dimension
2409
2410    local pstype, psdim, b, vdim, vs, func, pre,
2411          Wvectors, mb, compl, gen, bas, img, canbas, zero, basimgs,
2412          ps2, map, hom, a, m, newform, f, perp;
2413
2414    pstype := PolarSpaceType(ps);
2415    psdim := ps!.dimension;
2416    f := ps!.basefield;
2417    b := v!.obj;
2418    vdim := v!.type;
2419    if vdim = 1 then b:=[b]; fi;
2420    perp := PolarMap(ps);
2421    vs := VectorSpace(f, perp(v)!.obj);
2422
2423    ## if v has dimension j, then the new geometry
2424    ## has dimension psdim - 2*vdim
2425
2426    Wvectors:= b;
2427    mb:= MutableBasis( f, Wvectors );
2428    compl:= [];
2429    for gen in BasisVectors( Basis( vs ) ) do
2430        if not IsContainedInSpan( mb, gen ) then
2431          Add( compl, gen );
2432          CloseMutableBasis( mb, gen );
2433        fi;
2434    od;
2435    bas := BasisNC( vs, Concatenation( Wvectors, compl ) );
2436
2437    # Compute the linear mapping by images.
2438    img:= FullRowModule( f, Length( compl ) );
2439    canbas:= CanonicalBasis( img );
2440    zero:= Zero( img );
2441    # Matrix of the linear transformation we want
2442    basimgs:= Concatenation( List( Wvectors, v -> zero ),
2443                             BasisVectors( canbas ) );
2444    ConvertToMatrixRep(basimgs, f);
2445    a := compl;
2446
2447    if HasQuadraticForm( ps ) then
2448       m := QuadraticForm(ps)!.matrix;
2449       newform := QuadraticFormByMatrix(a * m * TransposedMat(a), f);
2450    else
2451       m := SesquilinearForm(ps)!.matrix;
2452       if pstype = "hermitian" then
2453          newform := HermitianFormByMatrix(a * m * TransposedMat(a^CompanionAutomorphism(ps)), f);
2454       else
2455          newform := BilinearFormByMatrix(a * m * TransposedMat(a), f);
2456       fi;
2457    fi;
2458    ps2 := PolarSpace( newform );
2459
2460    func := function( x )
2461              local y;
2462              y := List(x!.obj,i-> Coefficients(bas,i))*basimgs;
2463              y := SemiEchelonMat(y)!.vectors;
2464              if x!.type - vdim = 1 then
2465                 y := y[1];
2466                 ConvertToVectorRepNC(y, f);
2467              else
2468                 ConvertToMatrixRepNC(y, f);
2469              fi;
2470              return VectorSpaceToElement(ps2, y);
2471            end;
2472
2473    pre := function( y )
2474              local x;
2475              x := y!.obj;
2476              if y!.type = 1 then x := [x]; fi;
2477              x := Concatenation(x * compl, StructuralCopy( b ));
2478              ConvertToMatrixRepNC(x, f);
2479              return VectorSpaceToElement(ps, x);
2480            end;
2481    map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(ps),
2482                                      ElementsOfIncidenceStructure(ps2), func, false, pre );
2483    return map;
2484  end );
2485
2486#############################################################################
2487#
2488# Klein correspondence
2489#
2490#############################################################################
2491
2492#############################################################################
2493# two helper operations. They can be used by the users, but on the other hand
2494# the form of the Klein quadric is fixed, so this is against the philiosphy of
2495# FinInG, at least for user functions. Also, there are no checks. So we will
2496# describe these in the technical section of the documentation.
2497#############################################################################
2498
2499# CHECKED 28/09/11 jdb
2500# changed 28/3/14 jdb
2501#############################################################################
2502#O  PluckerCoordinates( <lobj> )
2503# returns the Plucker coordinates of the line represented by <lobj>. We accept
2504# a matrix representing the line. No check on whether this is a line of PG(3,q),`
2505# so use with care. cmat note: whether l is a cmat or not, coords will be a plain
2506# list (so no cvec). This is the internal version accepting a matrix.
2507##
2508InstallMethod( PluckerCoordinates,
2509	"for a matrix representing a line of PG(3,q)",
2510    [ IsMatrix ],
2511	function( lobj )
2512		local pij, u, v, coords;
2513		pij := function(u,v,i,j)
2514			return u[i]*v[j] - u[j] * v[i];
2515		end;
2516		u := lobj[1];
2517		v := lobj[2];
2518		coords := [pij(u,v,1,2),pij(u,v,1,3),pij(u,v,1,4),
2519			pij(u,v,2,3),pij(u,v,4,2),pij(u,v,3,4)];
2520		return coords;
2521	end );
2522
2523# CHECKED 28/09/11 jdb
2524# changed 28/3/14 jdb
2525#############################################################################
2526#O  PluckerCoordinates( <line> )
2527# returns the Plucker coordinates of the line represented by <lobj>. We accept
2528# a matrix representing the line. No check on whether this is a line of PG(3,q),`
2529# so use with care. cmat note: whether l is a cmat or not, coords will be a plain
2530# list (so no cvec). This is the user version accepting a subspace of a projective
2531# space.
2532##
2533#InstallMethod( PluckerCoordinates,
2534#	"for a matrix representing a line of PG(3,q)",
2535#    [ IsSubspaceOfProjectiveSpace ],
2536#	function( l )
2537#		local pij, u, v, coords,lobj;
2538#		if l!.type <> 2 or Dimension(l!.geo) <> 3 then
2539#           Error(" <l> must be a line of a PG(3,q)");
2540#        fi;
2541#        lobj := l!.obj;
2542#        pij := function(u,v,i,j)
2543#			return u[i]*v[j] - u[j] * v[i];
2544#		end;
2545#		u := lobj[1];
2546#		v := lobj[2];
2547#		coords := [pij(u,v,1,2),pij(u,v,1,3),pij(u,v,1,4),
2548#			pij(u,v,2,3),pij(u,v,4,2),pij(u,v,3,4)];
2549#		return coords;
2550#	end );
2551
2552
2553# CHECKED 28/09/11 jdb
2554# changed 28/3/14 jdb
2555#############################################################################
2556#O  InversePluckerCoordinates( <var> )
2557# returns a list of two vectors spanning the line with Plucker coordinates <var>
2558# no check on whether this is really a line of the particular Q+(5,q):X0X5+X1X4+X2X3
2559# caution: the list l is not Triangulized!
2560# cmat note: l will be an ordinary matrix.
2561##
2562InstallMethod( InversePluckerCoordinates,
2563	"for a vector repr. a point of Q+(5,q): X0X5+X1X4+X2X3=0",
2564    [ IsVector ],
2565
2566   ## The point must satisfy the Pluecker relation x1x6+x2x5+x3x4=0.
2567	function( x )
2568		local pairs, i, pair, l, f, zero;
2569		pairs := [[1,2],[1,3],[1,4],[2,3],[4,2],[3,4]];
2570		i := PositionNonZero(x);
2571		pair := pairs[i];
2572		zero := Zero(x[1]);
2573        l := [];
2574		if 1 in pair then
2575			Add(l, [ zero, x[1], x[2], x[3] ]);
2576		fi;
2577		if 2 in pair then
2578			Add(l, [ x[1], zero, -x[4], x[5] ]);
2579		fi;
2580		if 3 in pair then
2581			Add(l, [ x[2], x[4], zero, -x[6] ]);
2582		fi;
2583		if 4 in pair then
2584			Add(l, [ x[3], -x[5], x[6], zero ]);
2585		fi;
2586		return l;
2587	end );
2588
2589#############################################################################
2590# Klein correspondence: the user operations.
2591#############################################################################
2592
2593# added may 2014 jdb
2594#############################################################################
2595#O  PluckerCoordinates( <l> )
2596# returns the Plucker coordinates of the line <l>. The is the user variant
2597# of the operation. It accepts a line of PG(3,q), does some checking, and
2598# uses the helper operation.
2599##
2600InstallMethod( PluckerCoordinates,
2601	"for a line of PG(3,q)",
2602    [ IsSubspaceOfProjectiveSpace ],
2603	function( l )
2604		if Dimension(AmbientSpace(l)) <> 3 then
2605			Error("<l> is not a line of a three dimensional projective space");
2606		elif l!.type <> 2 then
2607			Error("<l> is not a line");
2608		fi;
2609		return PluckerCoordinates(l!.obj);
2610	end );
2611
2612# added may 2014 jdb
2613#############################################################################
2614#O  KleinCorrespondence( <f>, <computeintertwiner> )
2615# returns the well known morphism from the lines of PG(3,f), f a finite field
2616# to the points of Q+(5,q): x0x5+x1x4+x2x3 = 0. This is the bare essential
2617# of some geometry morphisms that follow. For didactical reasons, it looks
2618# useful to me to have this version that maps to a fixed hyperbolic quadric.
2619# Of course a flexible verion of this operation is also present.
2620##
2621# The interwiner: - the frobenius automorphism commutes with taking PluckerCoordinates
2622#					so we only have to deal with the matrices in the twiner/pretwiner
2623#					funcs.
2624#				  - A projectivity is known if its image of a frame is known. So:
2625#					going from PGL(4,q) -> PGO+(6,q): the standard basepoints of PG(5,q)
2626#					are points of the quadric, compute the lines (InversePlucker), their image,
2627#					, computer the point (Plucker), this gives the matrix of the element
2628#					of PGO+(5,q) where each row is determined up to a factor. It turns out
2629#					using order as in twinerfunc, the needed factors are always the same:
2630#					[1,1,1,1,1,-1]. This explains the '-' at newmat[6] line.
2631#					going from PGO+(5,q) -> PGL(4,q): cave canem: projectivities swapping
2632#					greek and latin planes do not induce a projectivity of PG(3,q)!
2633#					The principle is the same as above, just more work is needed to compute
2634#					the factors here (they seem not to be independant of the group element now).
2635#					Checking whether greek/latins are swapped is done by mapping a point
2636#					to a generator, computing the image, mapping back, and see whether
2637#					the three lines span PG(3,q). If so, we have the point, if not,
2638#					the image of the point is a plane and the projectivity of Q+(5,q)
2639#					swaps the greeks/latins and induces in fact a correlation of PG(3,q).
2640##
2641InstallMethod( KleinCorrespondence,
2642	"for a finite field and a boolean",
2643    [ IsField, IsBool ],
2644	function( f, computeintertwiner )
2645		local i, form, map, pg, mat, pre, plucker, ps, inv, one, twinerfunc,
2646		twinerprefun, coll1, gens1, coll2, gens2, hom, id;
2647
2648		one := One(f);
2649
2650    ## The form for the resulting Plucker coordinates is
2651    ## x1x6+x2x5+x3x4 = 0
2652
2653		mat := NullMat(6, 6, f);
2654		for i in [1..3] do
2655			mat[i][7-i] := one;
2656		od;
2657		form := QuadraticFormByMatrix(mat, f);
2658		ps := PolarSpace( form );
2659		pg := ProjectiveSpace(3,f);
2660
2661		plucker :=
2662           function( l )
2663             local pt1;
2664             pt1 := PluckerCoordinates( l!.obj );
2665             return VectorSpaceToElement(ps, pt1);
2666           end;
2667
2668		inv :=
2669           function( x )
2670             local l;
2671             l := InversePluckerCoordinates( x!.obj );
2672             return VectorSpaceToElement(pg, l);
2673           end;
2674
2675		map := GeometryMorphismByFunction(Lines(pg), Points(ps), plucker, inv);
2676		SetIsBijective(map, true);
2677
2678		twinerfunc := function(g)
2679			local mat,newmat;
2680			mat := Unpack(g!.mat);
2681			newmat := [];
2682			newmat[1] := PluckerCoordinates([mat[2],mat[1]]);
2683			newmat[2] := PluckerCoordinates([mat[3],mat[1]]);
2684			newmat[3] := PluckerCoordinates([mat[4],mat[1]]);
2685			newmat[4] := PluckerCoordinates([mat[3],mat[2]]);
2686			newmat[5] := PluckerCoordinates([mat[4],-mat[2]]);
2687			newmat[6] := -PluckerCoordinates([-mat[4],mat[3]]);
2688			return ProjElWithFrob(newmat,g!.frob,f);
2689		end;
2690
2691		id := IdentityMat(4, f);
2692
2693		twinerprefun := function( g )
2694			local mat, newmat, lines, pts, ipts, ilines, e, x, ept,
2695				ielines, ie, cs, iept;
2696			mat := Unpack(g!.mat);
2697			lines:= [];
2698			lines[1] := [[id[1],id[2]],[id[1],id[3]],[id[1],id[4]]];
2699			lines[2] := [[id[2],id[3]],[id[2],id[4]]];
2700			lines[3] := [[id[3],id[4]],[id[3],id[1]]];
2701			lines[4] := [[id[4],id[1]],[id[4],id[2]]];
2702			pts := List(lines,x->List(x,y->PluckerCoordinates(y)));
2703			ipts := List(pts,x->x*mat);
2704			ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
2705			if Rank(Union(ilines[1])) = 3 then
2706				Info(InfoFinInG, 1, "<el> is not inducing a collineation of PG(3,q)");
2707				return fail;
2708			fi;
2709			ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
2710			newmat := List(ipts,x->x[1]);
2711			e := [1,1,1,1]*one;
2712			ept := List([[e,id[1]],[e,id[2]]],y->PluckerCoordinates(y));
2713			iept := List(ept,x->x*mat);
2714			ielines := List(iept,x->InversePluckerCoordinates(x));
2715			ie := SumIntersectionMat(ielines[1],ielines[2])[2];
2716			cs := SolutionMat(newmat,ie[1]);
2717			for i in [1..4] do
2718				newmat[i] := newmat[i]*cs[i];
2719			od;
2720			return ProjElWithFrob(newmat,g!.frob,f);
2721		end;
2722
2723		if computeintertwiner then
2724			coll1 := CollineationGroup(PG(3,f));
2725			gens1 := GeneratorsOfGroup(coll1);
2726			gens2 := List(gens1, twinerfunc);
2727			coll2 := GroupWithGenerators(gens2);
2728			hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
2729			SetIntertwiner( map, hom );
2730		fi;
2731
2732		return map;
2733	end );
2734
2735#############################################################################
2736#O  KleinCorrespondence( <q> )
2737# returns KleinCorrespondence( GF(q), true )
2738##
2739InstallMethod( KleinCorrespondence,
2740	"for a prime power",
2741    [ IsPosInt ],
2742	q -> KleinCorrespondence(GF(q),true)
2743	);
2744
2745#############################################################################
2746#O  KleinCorrespondence( <q> )
2747# returns KleinCorrespondence( GF(q), computeintertwiner )
2748##
2749InstallMethod( KleinCorrespondence,
2750	"for a prime power and a boolean",
2751    [ IsPosInt, IsBool ],
2752	function(q, computeintertwiner )
2753		return KleinCorrespondence(GF(q),computeintertwiner);
2754	end );
2755
2756#############################################################################
2757#O  KleinCorrespondence( <f> )
2758# returns KleinCorrespondence( GF(q), true )
2759##
2760InstallMethod( KleinCorrespondence,
2761	"for a finite field",
2762    [ IsField ],
2763	f -> KleinCorrespondence(f,true)
2764	);
2765
2766
2767# CHECKED 28/09/11 jdb
2768#############################################################################
2769#O  KleinCorrespondence( <quadric>, <computeintertwiner> )
2770# returns the well known morphism from the lines of PG(3,q) to the points
2771# of Q+(5,q). Of course, the bilinear form determining the latter, can be chosen
2772# by the users. This method is exactly the same as the method for
2773#  KleinCorrespondence( <f>, <computeintertwiner> ), but adds base changes.
2774##
2775InstallMethod( KleinCorrespondence,
2776	"for a hyperbolic quadric and a boolean",
2777	[ IsClassicalPolarSpace, IsBool ],
2778	function( quadric, computeintertwiner )
2779		local f, i, form, map, pg, mat, twinerfunc, twinerprefun,
2780			pre, func, ps, iso, one, c, c1, c2, id, cinv, coll1, gens1,
2781			coll2, gens2, hom;
2782		if ProjectiveDimension(quadric) <> 5 then
2783			Error("<ps> is not Klein's quadric");
2784		fi;
2785		if not IsHyperbolicQuadric(quadric) then
2786   			Error("<ps> is not Klein's quadric");
2787		fi;
2788		f := quadric!.basefield;
2789		one := One(f);
2790
2791    ## The form for the resulting Plucker coordinates is
2792    ## x1x6+x2x5+x3x4 = 0
2793
2794		mat := NullMat(6, 6, f);
2795		for i in [1..3] do
2796			mat[i][7-i] := one;
2797		od;
2798		form := QuadraticFormByMatrix(mat, f);
2799		ps := PolarSpace( form );
2800		c1 := BaseChangeToCanonical( form );
2801		if not IsCanonicalPolarSpace(quadric) then
2802			c2 := BaseChangeToCanonical(QuadraticForm(quadric));
2803			c := c2^-1 * c1;
2804		else
2805			c := c1;
2806		fi;
2807		cinv := c^-1;
2808		pg := ProjectiveSpace(3,f);
2809
2810		func := function( el )
2811			local pt1;
2812			if el!.type <> 2 then
2813				Error("<el> is not a line ");
2814			fi;
2815			pt1 := PluckerCoordinates( el!.obj );
2816			return VectorSpaceToElement(quadric, pt1 * cinv ); #base change is here
2817        end;
2818
2819		pre := function( el )
2820			local l;
2821			if el!.type <> 1 then
2822				Error("<el> is not a point ");
2823			fi;
2824			l := InversePluckerCoordinates( el!.obj * c ); #base change is here
2825			return VectorSpaceToElement(pg, l);
2826        end;
2827
2828		map := GeometryMorphismByFunction(Lines(pg), Points(quadric), func, pre);
2829		SetIsBijective(map, true);
2830
2831		twinerfunc := function(g)
2832			local mat,newmat,frob;
2833			mat := Unpack(g!.mat);
2834			frob := g!.frob;
2835			newmat := [];
2836			newmat[1] := PluckerCoordinates([mat[2],mat[1]]);
2837			newmat[2] := PluckerCoordinates([mat[3],mat[1]]);
2838			newmat[3] := PluckerCoordinates([mat[4],mat[1]]);
2839			newmat[4] := PluckerCoordinates([mat[3],mat[2]]);
2840			newmat[5] := PluckerCoordinates([mat[4],-mat[2]]);
2841			newmat[6] := -PluckerCoordinates([-mat[4],mat[3]]);
2842			return ProjElWithFrob(c * newmat * cinv^frob,frob,f); #base change is here.
2843		end;
2844
2845		id := IdentityMat(4, f);
2846
2847		twinerprefun := function( g )
2848			local mat, newmat, lines, pts, ipts, ilines, e, x, ept,
2849				ielines, ie, cs, iept, frob;
2850			frob := g!.frob;
2851			mat := cinv * Unpack(g!.mat) * c^frob;      #base change is here.
2852			lines:= [];
2853			lines[1] := [[id[1],id[2]],[id[1],id[3]],[id[1],id[4]]];
2854			lines[2] := [[id[2],id[3]],[id[2],id[4]]];
2855			lines[3] := [[id[3],id[4]],[id[3],id[1]]];
2856			lines[4] := [[id[4],id[1]],[id[4],id[2]]];
2857			pts := List(lines,x->List(x,y->PluckerCoordinates(y)));
2858			ipts := List(pts,x->x*mat);
2859			ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
2860			if Rank(Union(ilines[1])) = 3 then
2861				Info(InfoFinInG, 1, "<el> is not inducing a collineation of PG(3,q)");
2862				return fail;
2863			fi;
2864			ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
2865			newmat := List(ipts,x->x[1]);
2866			e := [1,1,1,1]*one;
2867			ept := List([[e,id[1]],[e,id[2]]],y->PluckerCoordinates(y));
2868			iept := List(ept,x->x*mat);
2869			ielines := List(iept,x->InversePluckerCoordinates(x));
2870			ie := SumIntersectionMat(ielines[1],ielines[2])[2];
2871			cs := SolutionMat(newmat,ie[1]);
2872			for i in [1..4] do
2873				newmat[i] := newmat[i]*cs[i];
2874			od;
2875			return ProjElWithFrob(newmat,g!.frob,f);
2876		end;
2877
2878		if computeintertwiner then
2879			coll1 := CollineationGroup(PG(3,f));
2880			gens1 := GeneratorsOfGroup(coll1);
2881			gens2 := List(gens1, twinerfunc);
2882			coll2 := GroupWithGenerators(gens2);
2883			hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
2884			SetIntertwiner( map, hom );
2885		fi;
2886
2887		return map;
2888
2889	end );
2890
2891#############################################################################
2892#O  KleinCorrespondence( <quadric> )
2893# returns KleinCorrespondence( <quadric>, true )
2894##
2895InstallMethod( KleinCorrespondence,
2896	"for a hyperbolic quadric",
2897    [ IsClassicalPolarSpace ],
2898	quadric -> KleinCorrespondence(quadric, true)
2899	);
2900
2901# added may 2014 jdb.
2902#############################################################################
2903#O  KleinCorrespondenceExtended( <f>, <computeintertwiner> )
2904# I had really a lot of fun with the above methods and the mathematics behind.
2905# Therefore I decided to include an "extension" which also computes the image
2906# of points and planes of PG(3,q) under the Klein correspondence. This morphism
2907# becomes an isomorphism from PG(3,q) as incidence geometry to the incidence
2908# geometry consisting of the points and the two systems of generators of Q+(5,q).
2909# All mathematics is explained in the KleinCorrespondence variants above. For the
2910# moment I recycle the interwiner from the appropriate KleinCorrespondence's.
2911# When I will have more time, I'll maybe implement the interwiner from the
2912# CorrelationCollineationGroup(PG(3,q)) to PGO+(6,q).
2913##
2914InstallMethod( KleinCorrespondenceExtended,
2915	"for a field and a boolean",
2916    [ IsField, IsBool ],
2917	function( f, computeintertwiner )
2918		local i, form, map, pg, mat, pre, func, ps, one, twinerfunc,
2919		twinerprefun, coll1, gens1, coll2, gens2, hom, em;
2920
2921		one := One(f);
2922
2923    ## The form for the resulting Plucker coordinates is
2924    ## x1x6+x2x5+x3x4 = 0
2925
2926		mat := NullMat(6, 6, f);
2927		for i in [1..3] do
2928			mat[i][7-i] := one;
2929		od;
2930		form := QuadraticFormByMatrix(mat, f);
2931		ps := PolarSpace( form );
2932		pg := ProjectiveSpace(3,f);
2933
2934		func := function( el )
2935			local t, vec, newvec, ptvec, elvec;
2936			t := el!.type;
2937			elvec := Unpack(el!.obj);
2938			if t = 1 then
2939				vec := BasisVectors(Basis(Lines(el)!.factorspace));
2940				newvec := [PluckerCoordinates([elvec,vec[1]]), PluckerCoordinates([elvec,vec[2]]), PluckerCoordinates([elvec,vec[3]])];
2941				return VectorSpaceToElement(ps,newvec);
2942			elif t = 2 then
2943				return VectorSpaceToElement(ps,PluckerCoordinates(elvec));
2944			elif t = 3 then
2945				newvec := [PluckerCoordinates( elvec{[1,2]}), PluckerCoordinates( elvec{[2,3]} ), PluckerCoordinates( elvec{[1,3]} )];
2946				return VectorSpaceToElement(ps,newvec);
2947			fi;
2948		end;
2949
2950		pre := function( el )
2951			local elvec,t,newvec,lines,space;
2952			t := el!.type;
2953			elvec := Unpack(el!.obj);
2954			if t = 1 then
2955				newvec := InversePluckerCoordinates( elvec );
2956				return VectorSpaceToElement(pg, newvec);
2957			elif t = 3 then
2958				newvec := List(elvec,x->InversePluckerCoordinates( x ));
2959				lines := List(newvec,x->VectorSpaceToElement(pg,x));
2960				space := Meet(lines);
2961				if IsEmptySubspace(space) then
2962					return Span(lines);
2963				else
2964					return space;
2965				fi;
2966			elif t = 2 then
2967				Error("Lines of Klein quadric have no preimage");
2968			fi;
2969		end;
2970
2971		map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(pg), ElementsOfIncidenceStructure(ps), func, pre);
2972		SetIsBijective(map, true);
2973
2974		if computeintertwiner then
2975			em := KleinCorrespondence(f,computeintertwiner);
2976			hom := Intertwiner(em);
2977			SetIntertwiner( map, hom );
2978		fi;
2979
2980		return map;
2981	end );
2982
2983#############################################################################
2984#O  KleinCorrespondenceExtended( <q> )
2985# returns KleinCorrespondenceExtended( GF(q), true )
2986##
2987InstallMethod( KleinCorrespondenceExtended,
2988	"for a prime power",
2989    [ IsPosInt ],
2990	q -> KleinCorrespondenceExtended(GF(q),true)
2991	);
2992
2993# added may 2014 jdb.
2994#############################################################################
2995#O  KleinCorrespondenceExtended( <quadric>, <computeintertwiner> )
2996# The same as KleinCorrespondenceExtended, but accepting a user defined
2997# hyperbolic quadric
2998##
2999InstallMethod( KleinCorrespondenceExtended,
3000	"for a field and a boolean",
3001    [ IsClassicalPolarSpace, IsBool ],
3002		function( quadric, computeintertwiner )
3003		local i, form, map, pg, mat, pre, func, ps, one, twinerfunc, f,
3004		twinerprefun, coll1, gens1, coll2, gens2, hom, em, c, c1, c2, cinv;
3005
3006		f := quadric!.basefield;
3007		one := One(f);
3008
3009    ## The form for the resulting Plucker coordinates is
3010    ## x1x6+x2x5+x3x4 = 0
3011
3012		mat := NullMat(6, 6, f);
3013		for i in [1..3] do
3014			mat[i][7-i] := one;
3015		od;
3016		form := QuadraticFormByMatrix(mat, f);
3017		ps := PolarSpace( form );
3018		pg := ProjectiveSpace(3,f);
3019
3020		c1 := BaseChangeToCanonical( form );
3021		if not IsCanonicalPolarSpace(quadric) then
3022			c2 := BaseChangeToCanonical(QuadraticForm(quadric));
3023			c := c2^-1 * c1;
3024		else
3025			c := c1;
3026		fi;
3027		cinv := c^-1;
3028
3029		func := function( el )
3030			local t, vec, newvec, ptvec, elvec;
3031			t := el!.type;
3032			elvec := Unpack(el!.obj);
3033			if t = 1 then
3034				vec := BasisVectors(Basis(Lines(el)!.factorspace));
3035				newvec := [PluckerCoordinates([elvec,vec[1]]), PluckerCoordinates([elvec,vec[2]]), PluckerCoordinates([elvec,vec[3]])];
3036				return VectorSpaceToElement(quadric,newvec * cinv); #base change is here...
3037			elif t = 2 then
3038				return VectorSpaceToElement(quadric,PluckerCoordinates(elvec) * cinv); # ... and here ...
3039			elif t = 3 then
3040				newvec := [PluckerCoordinates( elvec{[1,2]}), PluckerCoordinates( elvec{[2,3]} ), PluckerCoordinates( elvec{[1,3]} )];
3041				return VectorSpaceToElement(quadric,newvec * cinv); #... and here.
3042			fi;
3043		end;
3044
3045		pre := function( el )
3046			local elvec,t,newvec,lines,space;
3047			t := el!.type;
3048			elvec := Unpack(el!.obj) * c; # base change is here.
3049			if t = 1 then
3050				newvec := InversePluckerCoordinates( elvec );
3051				return VectorSpaceToElement(pg, newvec);
3052			elif t = 3 then
3053				newvec := List(elvec,x->InversePluckerCoordinates( x ));
3054				lines := List(newvec,x->VectorSpaceToElement(pg,x));
3055				space := Meet(lines);
3056				if IsEmptySubspace(space) then
3057					return Span(lines);
3058				else
3059					return space;
3060				fi;
3061			elif t = 2 then
3062				Error("Lines of Klein's quadric have no preimage in the source of <em>");
3063			fi;
3064		end;
3065
3066		map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(pg), ElementsOfIncidenceStructure(quadric), func, pre);
3067		SetIsBijective(map, true);
3068
3069		if computeintertwiner then
3070			em := KleinCorrespondence(quadric,true);
3071			hom := Intertwiner(em);
3072			SetIntertwiner( map, hom );
3073		fi;
3074
3075		return map;
3076	end );
3077
3078#############################################################################
3079#O  KleinCorrespondenceExtended( <quadric> )
3080# returns KleinCorrespondenceExtended( quadric, true )
3081##
3082InstallMethod( KleinCorrespondenceExtended,
3083	"for a prime power",
3084    [ IsClassicalPolarSpace ],
3085	quadric -> KleinCorrespondenceExtended(quadric,true)
3086	);
3087
3088
3089#############################################################################
3090#
3091# DUALITIES between W(q) and Q(4,q) and H(3,q^2) and Q-(5,q)
3092#
3093#############################################################################
3094
3095#############################################################################
3096# two helper operations. These were derived from the previous version of
3097# NaturalDuality. The new NaturalDuality will interface these.
3098#############################################################################
3099
3100# Added april 2014. jdb
3101#############################################################################
3102#O  NaturalDualitySymplectic( <w>, <q4q>, <computeintertwiner>, <reverse> )
3103# returns the well known isomorphism between W(3,q) and Q(4,q).
3104# Both polar spaces may be user defined.
3105# Setup: - Plucker Coordinates map lines of PG(3,q) on points of Q+(5,q): X0X5+X1X4+X2X3 = 0
3106#        - restricting to lines of W(3,q), the range is the points of Q(4,q): X0^2 = X1X4+X2X3, which is the above
3107#          intersecting with the hyperplane X0+X5 = 0.
3108#        - When using PluckerCoordinates, we only need the first 5 coordinates. When using InversePluckerCoordinates,
3109#          we make the sixth coordinate equal to minus the first.
3110#        - points of W(3,q): take its pole under the polarity associated with W(3,q), this defines a plane.
3111#          From this plane's underlying object, we have three lines -> three points of Q+(5,q) spanning a generator
3112#		   then intersect this generator with X0+X5=0 to find the line of Q(4,q).
3113#        - lines of Q(4,q): take the two spanning points -> two lines of W -> Meet of these lines is the point
3114#          we are looking for.
3115#        - some base changes are necessary.
3116#        - all the linear algebra is hard coded.
3117#        - the intertwiners: inspired by the intertwiners of the Klein Correspondence.
3118#		 - except for the filters, there is not real check whether the input the correct GQ.
3119#############################################################################
3120##
3121InstallMethod( NaturalDualitySymplectic,
3122	"for a symplectic GQ and a parabolic quadric",
3123	[ IsClassicalGQ, IsClassicalGQ, IsBool, IsBool ],
3124	function( w, q4q, computeintertwiner, reverse )
3125    local f, one, mat, form_quadric, quadric, data, form_w, cq4q, cw, can,
3126        func, pre, formw, c1, c2, delta, hyp, coll1, coll2, gens1, gens2, hom,
3127		twinerfunc, twinerprefun, cq4qinv, cwinv, id;
3128    f := w!.basefield;
3129    one := One(f);
3130    mat := NullMat(5, 5, f);
3131    mat[1][1] := one;
3132    mat[2][5] := -one;
3133    mat[3][4] := -one;
3134    form_quadric := QuadraticFormByMatrix(mat, f);
3135	c1 := BaseChangeToCanonical( form_quadric );
3136    if not IsCanonicalPolarSpace(q4q) then
3137        c2 := BaseChangeToCanonical(QuadraticForm(q4q));
3138        cq4q := c2^-1 * c1;
3139    else
3140        cq4q := c1;
3141    fi;
3142    cq4qinv := cq4q^-1;
3143	formw := SesquilinearForm( w );
3144    delta := PolarityOfProjectiveSpace(w);
3145    hyp := IdentityMat(6,f){[1..5]}; #this and next are in fact HyperplaneByDualCoorindates([1,0,0,0,0,1]..)
3146    hyp[1][6] := -one;
3147   	if IsCanonicalPolarSpace( w ) then
3148        func := function( el )
3149            local list,plane,vec;
3150            if el!.type = 2 then #dealing with a line
3151                return VectorSpaceToElement(q4q, PluckerCoordinates(el!.obj){[1..5]} * cq4q^-1 );
3152            else
3153                plane := el^delta;
3154                vec := Unpack(plane!.obj); #we have the plane, next line: use three lines in it.
3155                list := [ PluckerCoordinates(vec{[1,2]}), PluckerCoordinates(vec{[2,3]}), PluckerCoordinates(vec{[1,3]}) ];
3156       			plane := SumIntersectionMat(list, hyp)[2]; #hard coded Meet operation :-)
3157                list := List(plane,x->x{[1..5]}*cq4q^-1); #remember that we only need the first five coordinates, and do not forget base change
3158                return VectorSpaceToElement(q4q, list);
3159            fi;
3160        end;
3161        pre := function( el )
3162            local vec;
3163            if el!.type = 1 then #dealing with a point
3164                vec := Unpack(el!.obj)*cq4q;
3165                vec[6] := -vec[1];
3166                return VectorSpaceToElement(w, InversePluckerCoordinates(vec) );
3167            else
3168                vec := Unpack(el!.obj)*cq4q;
3169                vec[1][6] := -vec[1][1];
3170                vec[2][6] := -vec[2][1];
3171                return VectorSpaceToElement(w, SumIntersectionMat(InversePluckerCoordinates(vec[1]), InversePluckerCoordinates(vec[2]))[2] );
3172            fi;
3173        end;
3174    else
3175		cw := BaseChangeToCanonical( formw );
3176        func := function( el )
3177            local list,plane,vec;
3178                if el!.type = 2 then #dealing with a line
3179                return VectorSpaceToElement(q4q, PluckerCoordinates(Unpack(el!.obj)*cw^-1){[1..5]} * cq4q^-1);
3180            else
3181                plane := el^delta;
3182                vec := Unpack(plane!.obj)*cw^-1;
3183                list := [ PluckerCoordinates(vec{[1,2]}), PluckerCoordinates(vec{[2,3]}), PluckerCoordinates(vec{[1,3]}) ];
3184       			plane := SumIntersectionMat(list, hyp)[2];
3185                list := List(plane,x->x{[1..5]}*cq4q^-1);
3186                return VectorSpaceToElement(q4q, list);
3187            fi;
3188        end;
3189        pre := function( el )
3190            local vec;
3191            if el!.type = 1 then #dealing with a point
3192                vec := Unpack(el!.obj)*cq4q;
3193                vec[6] := -vec[1];
3194                return VectorSpaceToElement(w, InversePluckerCoordinates(vec) * cw);
3195            else;
3196                vec := Unpack(el!.obj)*cq4q;
3197                vec[1][6] := -vec[1][1];
3198                vec[2][6] := -vec[2][1];
3199                return Meet( VectorSpaceToElement(w, InversePluckerCoordinates(vec[1]) * cw ),
3200                                VectorSpaceToElement(w, InversePluckerCoordinates(vec[2]) * cw ) );
3201            fi;
3202        end;
3203    fi;
3204
3205	if not reverse then
3206		data := rec( func := func, pre := pre);
3207	else
3208		data := rec( func := pre, pre := func);
3209	fi;
3210
3211   	if computeintertwiner then
3212
3213		id := IdentityMat(4, f);
3214
3215		if IsCanonicalPolarSpace( w ) then
3216
3217			twinerfunc := function(g)
3218				local mat,newmat,frob;
3219				mat := Unpack(g!.mat);
3220				frob := g!.frob;
3221				newmat := [];
3222				newmat[2] := PluckerCoordinates([mat[3],mat[1]]){[1..5]};
3223				newmat[3] := PluckerCoordinates([mat[4],mat[1]]){[1..5]};
3224				newmat[4] := -PluckerCoordinates([-mat[3],mat[2]]){[1..5]};
3225				newmat[5] := PluckerCoordinates([mat[4],-mat[2]]){[1..5]};
3226				newmat[1] := PluckerCoordinates([mat[2]+mat[3],mat[1]+mat[4]]){[1..5]} - newmat[2] - newmat[5];
3227				return ProjElWithFrob(cq4q * newmat * cq4qinv^(frob^-1),frob,f); #base change is here.
3228				return newmat;
3229			end;
3230
3231			twinerprefun := function( g )
3232				local mat, newmat, lines, pts, ipts, ilines, e, x, ept,
3233					ielines, ie, cs, iept, frob, i, j;
3234				frob := g!.frob;
3235				mat := cq4qinv * Unpack(g!.mat) * cq4q^(frob^-1);
3236				lines:= [];
3237				lines[1] := [[id[1],id[3]],[id[1],id[4]]];
3238				lines[2] := [[id[2],id[3]],[id[2],id[4]]];
3239				lines[3] := [[id[3],id[1]],[id[3],id[2]]];
3240				lines[4] := [[id[4],id[1]],[id[4],id[2]]];
3241				pts := List(lines,x->List(x,y->PluckerCoordinates(y){[1..5]}));
3242				ipts := List(pts,x->x*mat);
3243				for i in [1..Length(ipts)] do
3244					for j in [1..2] do
3245						ipts[i][j][6] := -ipts[i][j][1];
3246					od;
3247				od;
3248				ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
3249				ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
3250				newmat := List(ipts,x->x[1]);
3251				e := [1,1,1,1]*one;
3252				ept := List([[e,id[1]+id[4]],[e,id[1]+id[2]]],y->PluckerCoordinates(y){[1..5]});
3253				iept := List(ept,x->x*mat);
3254				iept[1][6] := -iept[1][1];
3255				iept[2][6] := -iept[2][1];
3256
3257				ielines := List(iept,x->InversePluckerCoordinates(x));
3258				ie := SumIntersectionMat(ielines[1],ielines[2])[2];
3259				cs := SolutionMat(newmat,ie[1]);
3260				for i in [1..4] do
3261					newmat[i] := newmat[i]*cs[i];
3262				od;
3263				return ProjElWithFrob(newmat,g!.frob,f);
3264			end;
3265
3266		else
3267
3268			cw := BaseChangeToCanonical( SesquilinearForm(w) );
3269			cwinv := cw^-1;
3270
3271			twinerfunc := function(g)
3272				local mat,newmat,frob;
3273				frob := g!.frob;
3274				mat := cw * Unpack(g!.mat) * cwinv^(frob^-1);
3275				newmat := [];
3276				newmat[2] := PluckerCoordinates([mat[3],mat[1]]){[1..5]};
3277				newmat[3] := PluckerCoordinates([mat[4],mat[1]]){[1..5]};
3278				newmat[4] := -PluckerCoordinates([-mat[3],mat[2]]){[1..5]};
3279				newmat[5] := PluckerCoordinates([mat[4],-mat[2]]){[1..5]};
3280				newmat[1] := PluckerCoordinates([mat[2]+mat[3],mat[1]+mat[4]]){[1..5]} - newmat[2] - newmat[5];
3281				return ProjElWithFrob(cq4q * newmat * cq4qinv^(frob^-1),frob,f); #base change is here.
3282				#return newmat;
3283			end;
3284
3285			twinerprefun := function( g )
3286				local mat, newmat, lines, pts, ipts, ilines, e, x, ept,
3287					ielines, ie, cs, iept, frob, i, j;
3288				frob := g!.frob;
3289				mat := cq4qinv * Unpack(g!.mat) * cq4q^(frob^-1);
3290				lines:= [];
3291				lines[1] := [[id[1],id[3]],[id[1],id[4]]];
3292				lines[2] := [[id[2],id[3]],[id[2],id[4]]];
3293				lines[3] := [[id[3],id[1]],[id[3],id[2]]];
3294				lines[4] := [[id[4],id[1]],[id[4],id[2]]];
3295				pts := List(lines,x->List(x,y->PluckerCoordinates(y){[1..5]}));
3296				ipts := List(pts,x->x*mat);
3297				for i in [1..Length(ipts)] do
3298					for j in [1..2] do
3299						ipts[i][j][6] := -ipts[i][j][1];
3300					od;
3301				od;
3302				ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
3303				ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
3304				newmat := List(ipts,x->x[1]);
3305				e := [1,1,1,1]*one;
3306				ept := List([[e,id[1]+id[4]],[e,id[1]+id[2]]],y->PluckerCoordinates(y){[1..5]});
3307				iept := List(ept,x->x*mat);
3308				iept[1][6] := -iept[1][1];
3309				iept[2][6] := -iept[2][1];
3310
3311				ielines := List(iept,x->InversePluckerCoordinates(x));
3312				ie := SumIntersectionMat(ielines[1],ielines[2])[2];
3313				cs := SolutionMat(newmat,ie[1]);
3314				for i in [1..4] do
3315					newmat[i] := newmat[i]*cs[i];
3316				od;
3317				return ProjElWithFrob(cwinv * newmat * cw^(frob^-1),g!.frob,f);
3318			end;
3319		fi;
3320		if not reverse then
3321			data.twinerfunc := twinerfunc;
3322			data.twinerprefun := twinerprefun;
3323		else
3324			data.twinerfunc := twinerprefun;
3325			data.twinerprefun := twinerfunc;
3326		fi;
3327	fi;
3328
3329    return data;
3330 end );
3331
3332# Added april 2014. jdb
3333#############################################################################
3334#O  NaturalDualityHermitian( <h>, <q5q>, <computeintertwiner>, <reverse> )
3335# returns the well known isomorphism between H(3,q^2) and Q-(5,q).
3336# Both polar spaces may be user defined.
3337# Setup: - Plucker Coordinates map lines of PG(3,q^2) on points of Q+(5,q^2): X0X5+X1X4+X2X3 = 0
3338#        - restricting to lines of H(3,q^2), the range is the points of Q-(5,q). The details (thanks jb for pointing these out!)
3339#			1. Suppose l is a line of H(3,q^2), pl its Plucker coordinates.
3340#			then up to a scalar, pl is of the form (x,y,z,z^q,y^q,x^q). We first make sure that
3341#			our vector is really in this form (up to no scalar!).
3342#           2. The projectivity of PG(5,q^2) which maps the Plucker coordinates of the lines of H(3,q^2)
3343#           to PG(5,q). See "Finite Projective Spaces of Three Dimensions" by Hirschfeld and Thas (section 19.2).
3344#			is defined.
3345#			3. Applying this collineation on plucker coordinates (in the right form, see above), makes
3346#			sure this is a point of the Elliptic Quadric Q-(5,q): x1^2+x2^2+x3^2+x4^2+x5^2+x6^2 - e(x1x6+x2x5+x3x4)
3347#			4. The result pl*x might be over GF(q) modulo a GF(q^2)
3348#			factor, then this causes problems in the VectorSpaceToElement
3349#			call, since using cvec/cmat, now requires this to be a vector
3350#			over GF(q). So we have to normalize first, then do the base change
3351#			then use VectorSpaceToElement.
3352#		 - the inverse is easier: the combination of InversePluckerCoordinates and xinv.
3353#		 - points of H(3,q^2): take its pole under the polarity associated with H(3,q^2), this defines a plane.
3354#          From this plane's underlying object, we have three lines -> three points of Q+(5,q^2) spanning a generator.
3355#		   Apply x on this generator and the intersection with the standard PG(5,q) yields a line of Q^-(5,q).
3356#		   To compute this intersection we first blow up, use the hard coded Meet, and then Shrink.
3357#        - lines of Q^-(5,q): take the two spanning points -> two lines of H -> Meet of these lines is the point
3358#          we are looking for.
3359#        - some base changes are necessary.
3360#        - all the linear algebra is hard coded.
3361#		 - except for the filters, there is not real check whether the input the correct GQ.
3362#        - the intertwiners: I can tell you: this is pain in the ass!
3363#        - twinerfun: from PGammaU(4,q^2) to PGammaO-(6,q):
3364#           1. part one is taken from the Klein correspondence. This gives an element of
3365#               PGammaO+(6,q^2), stabilizing some pointset that is actually a Q-(5,q).
3366#               We know the collineation x, and use it to make this a collineation of the Q-(5,q)
3367#           2. The frobenius automorphism of this collineation is just the acion of frob on the
3368#               subfield.
3369#        - twinerprefun:
3370#           1. part one goes from Q-(5,q) to Q+(5,q^2). Note that we need to compute frob2 from
3371#            the given frob, since although frob acts also on the big field, its inverse is not
3372#            the same, and this is really important!
3373#           2. part two starts a bit like the Klein correspondence. If the greeks/latins are
3374#            swapped by the computed collineation of Q+, we apply the special collineation.
3375#            this swaps back the greeks/latins, and stabilizes the Q-(5,q). Now we can use the
3376#            same code as in the Klein correspondence, modulo the switch field automorphism when
3377#              necessary.
3378#############################################################################
3379##
3380InstallMethod( NaturalDualityHermitian,
3381	"for H(3,q^2) and Q-(5,q)",
3382	[ IsClassicalGQ,  IsClassicalGQ, IsBool, IsBool ],
3383	function( h, q5q, computeintertwiner, reverse )
3384    ## The way this works is that we map the lines of h to the canonical H(3,q^2),
3385    ## which Klein corresponds to points of Q+(5,q^2) using the usual plucker map.
3386    ## This subgeometry is then mapped to PG(5,q), where we can associate it to
3387    ## Q-(5,q).
3388
3389    #local f, frob, q, one, iso, eq, pg5, alpha, mat1, mat2, x, xinv,
3390    #      func, pre, i, map, e, ps, form, iso2;
3391
3392    local f, frob, q, one, alpha, e, mat1, mat2, i, form_quadric, c1, c2, cq5q,
3393            func, pre, data, x, xinv, formh, ch, chinv, bmat, delta, basis, twinerfunc,
3394            twinerprefun, coll1,coll2, gens1, gens2, cq5qinv, hom, id, id6, special, theta;
3395
3396    f := h!.basefield;
3397    frob := FrobeniusAutomorphism(f);
3398    q := Sqrt(Size(f));
3399    one := One(f);
3400
3401    #first set up the elliptic quadric we get if we start from the standard hermitian polar space.
3402
3403    alpha := First(f, a -> (a^q)^2 <> a^2);
3404    e := alpha^(q-1) + alpha^(1-q);
3405
3406    ## The form for the Elliptic Quadric that we get is
3407    ## x1^2+x2^2+x3^2+x4^2+x5^2+x6^2 - e(x1x6+x2x5+x3x4)
3408
3409    mat1 := IdentityMat(6, GF(q));
3410    mat2 := NullMat(6, 6, f);
3411    for i in [1..3] do
3412       mat2[2*i-1][8-2*i] := one;
3413    od;
3414    mat1 := mat1 - e * mat2;
3415    form_quadric := QuadraticFormByMatrix(mat1, GF(q));
3416
3417    # do the necessary base changes.
3418
3419	c1 := BaseChangeToCanonical( form_quadric );
3420    if not IsCanonicalPolarSpace(q5q) then
3421        c2 := BaseChangeToCanonical(QuadraticForm(q5q));
3422        cq5q := c2^-1 * c1;
3423    else
3424        cq5q := c1;
3425    fi;
3426    cq5qinv := cq5q^-1;
3427
3428	#see comments in header which collineation x represents.
3429
3430	mat1 := IdentityMat(6, f) * alpha;
3431    mat2 := NullMat(6, 6, f);
3432    for i in [1..6] do
3433       mat2[i][7-i] := one;
3434    od;
3435    x := mat1 + mat2 * alpha^q;
3436    x := List(x,y->y);
3437    xinv := x^-1;
3438
3439	formh := SesquilinearForm( h );
3440
3441    delta := PolarityOfProjectiveSpace(h);
3442	bmat := NullMat(6,12,GF(q));
3443	for i in [1..6] do
3444		bmat[i][2*i-1] := one;
3445	od;
3446	basis := Basis(AsVectorSpace(GF(q),GF(q^2)));
3447
3448    if IsCanonicalPolarSpace( h ) then
3449       func := function( el )
3450            local pl, mu, pos, vec, plane, bgen, list;
3451
3452			if el!.type = 2 then #dealing with a line
3453				pl := PluckerCoordinates( Unpack(el!.obj) ); #the Unpack might be unnecessary, but harmless anyway.
3454				#see the comments in the header for what we do here.
3455				pos := PositionNonZero( pl );
3456				mu := First( AsList(f), m -> m^(q-1) = pl[pos] / pl[7-pos]);
3457				pl := mu * pl;
3458				vec := pl*x;
3459				MultRowVector(vec,Inverse( vec[PositionNonZero(vec)] ));
3460				return VectorSpaceToElement(q5q, vec * cq5q^-1);
3461			else
3462				plane := el^delta;
3463                vec := Unpack(plane!.obj); #we have the plane, next line: use three lines in it.
3464                list := [ PluckerCoordinates(vec{[1,2]}), PluckerCoordinates(vec{[2,3]}), PluckerCoordinates(vec{[1,3]}) ];
3465				bgen := BlownUpMat(basis,list*x);
3466				list := SumIntersectionMat(bgen, bmat)[2]; #hard coded Meet operation :-)
3467				vec := List(list,x->ShrinkVec(GF(q^2),GF(q),x));
3468                return VectorSpaceToElement(q5q, vec * cq5q^-1);
3469			fi;
3470		end;
3471
3472        pre := function( el )
3473			local vec ;
3474            if el!.type = 1 then #dealing with a point
3475				vec := Unpack(el!.obj)*cq5q;
3476				return VectorSpaceToElement(h, InversePluckerCoordinates(vec * xinv) );
3477			else
3478				vec := (Unpack(el!.obj)*cq5q)*xinv;
3479                return VectorSpaceToElement(h, SumIntersectionMat(InversePluckerCoordinates(vec[1]), InversePluckerCoordinates(vec[2]))[2] );
3480			fi;
3481		end;
3482    else
3483   		ch := BaseChangeToCanonical( formh );
3484        func := function( el )
3485            local pl, mu, pos, vec, plane, bgen, list;
3486   			if el!.type = 2 then #dealing with a line
3487				pl := PluckerCoordinates( Unpack(el!.obj)*ch^-1 );
3488				pos := PositionNonZero( pl );
3489				mu := First( AsList(f), m -> m^(q-1) = pl[pos] / pl[7-pos]);
3490				pl := mu * pl;
3491				vec := pl*x;
3492				MultRowVector(vec,Inverse( vec[PositionNonZero(vec)] ));
3493				return VectorSpaceToElement(q5q, vec * cq5q^-1);
3494			else
3495				plane := el^delta;
3496                vec := Unpack(plane!.obj)*ch^-1; #we have the plane, next line: use three lines in it.
3497                list := [ PluckerCoordinates(vec{[1,2]}), PluckerCoordinates(vec{[2,3]}), PluckerCoordinates(vec{[1,3]}) ];
3498				bgen := BlownUpMat(basis,list*x);
3499				list := SumIntersectionMat(bgen, bmat)[2]; #hard coded Meet operation :-)
3500				vec := List(list,x->ShrinkVec(GF(q^2),GF(q),x));
3501                return VectorSpaceToElement(q5q, vec * cq5q^-1);
3502			fi;
3503		end;
3504
3505		pre := function( el )
3506            local vec;
3507            if el!.type = 1 then #dealing with a point
3508				vec := Unpack(el!.obj)*cq5q;
3509				return VectorSpaceToElement(h, InversePluckerCoordinates(vec * xinv) * ch );
3510			else
3511				vec := (Unpack(el!.obj)*cq5q)*xinv;
3512                return VectorSpaceToElement(h, SumIntersectionMat(InversePluckerCoordinates(vec[1]), InversePluckerCoordinates(vec[2]))[2] * ch );
3513			fi;
3514		end;
3515
3516    fi;
3517
3518    #map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(h), ElementsOfIncidenceStructure(q5q), func, pre);
3519    #SetIsBijective( map, true );
3520
3521	if not reverse then
3522		data := rec( func := func, pre := pre);
3523	else
3524		data := rec( func := pre, pre := func);
3525	fi;
3526
3527	if computeintertwiner then
3528		id := IdentityMat(4,f);
3529		id6 := IdentityMat(6,f);
3530		special := List([1..6],i->id6[7-i]);
3531		i := Log(q,Characteristic(f));
3532		theta := FrobeniusAutomorphism(f)^i;
3533
3534		if IsCanonicalPolarSpace( h ) then
3535			twinerfunc := function(g)
3536				local mat,newmat,frob,newmat2,n,frob2,j,arg;
3537				frob := g!.frob;
3538				mat := Unpack(g!.mat);
3539				newmat := [];
3540				newmat[1] := PluckerCoordinates([mat[2],mat[1]]);
3541				newmat[2] := PluckerCoordinates([mat[3],mat[1]]);
3542				newmat[3] := PluckerCoordinates([mat[4],mat[1]]);
3543				newmat[4] := PluckerCoordinates([mat[3],mat[2]]);
3544				newmat[5] := PluckerCoordinates([mat[4],-mat[2]]);
3545				newmat[6] := -PluckerCoordinates([-mat[4],mat[3]]);
3546				newmat2 :=  xinv*newmat*x^(frob^-1);
3547				n := First(newmat2[1],x->not IsZero(x));
3548				newmat2 := newmat2/n;
3549				if not IsOne(frob) then
3550					j := Log(frob!.power,Characteristic(f));
3551				else
3552					j := 0;
3553				fi;
3554				frob2 := FrobeniusAutomorphism(GF(q))^(j mod q);
3555				arg := cq5q * newmat2 * cq5qinv^(frob2^-1);
3556				ConvertToMatrixRep(arg);
3557				return ProjElWithFrob(arg,frob2,GF(q));
3558			end;
3559
3560			twinerprefun := function( g )
3561				local mat, newmat, lines, pts, ipts, ilines, e, ept,
3562					ielines, ie, cs, iept, frob, frob2, j, n, switch;
3563					frob := g!.frob;
3564				#first setup the frobenius automorphism over GF(q^2)=f.
3565				if not IsOne(frob) then
3566					j := Log(frob!.power,Characteristic(f));
3567				else
3568					j := 0;
3569				fi;
3570				frob2 := FrobeniusAutomorphism(f)^j;
3571				#base change. Note that frob is used to change from different Q-(5,q)'s, frob2 for Q-(5,q) -> Q+(5,q^2)
3572				mat := x * (cq5qinv * Unpack(g!.mat) * cq5q^(frob^-1) ) * xinv^(frob2^-1);      #base change is here.
3573				#now from Q+(5,q^2) to H(3,q^2).
3574				lines:= [];
3575				pts := [];
3576				ipts := [];
3577				ilines := [];
3578				lines[1] := [[id[1],id[2]],[id[1],id[3]],[id[1],id[4]]];
3579				pts[1] := List(lines[1],y->PluckerCoordinates(y));
3580				ipts[1] := (pts[1]*mat);
3581				ilines[1] := List(ipts[1],y->InversePluckerCoordinates(y));
3582				switch := theta^0;
3583				if Rank(Union(ilines[1])) = 3 then
3584					#Print("switch\n");
3585					mat := (mat^theta) * special;
3586					frob2 := frob2 * theta;
3587					switch := theta;
3588				fi;
3589				n := First(mat[1],x->not IsZero(x));
3590				mat := mat/n;
3591				lines[1] := [[id[1],id[2]],[id[1],id[3]]];
3592				lines[2] := [[id[2],id[3]],[id[2],id[4]]];
3593				lines[3] := [[id[3],id[4]],[id[3],id[1]]];
3594				lines[4] := [[id[4],id[1]],[id[4],id[2]]];
3595				pts := List(lines,x->List(x,y->PluckerCoordinates(y)));
3596				ipts := List(pts,x->(x*mat)^switch);
3597				#ipts := List(pts,x->(x*mat));
3598				ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
3599				ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
3600				newmat := List(ipts,x->x[1]);
3601				e := [1,1,1,1]*one;
3602				ept := List([[e,id[1]],[e,id[2]]],y->PluckerCoordinates(y));
3603				iept := List(ept,x->(x*mat)^switch);
3604				#iept := List(ept,x->(x*mat));
3605				ielines := List(iept,x->InversePluckerCoordinates(x));
3606				ie := SumIntersectionMat(ielines[1],ielines[2])[2];
3607				cs := SolutionMat(newmat,ie[1]);
3608				for i in [1..4] do
3609					newmat[i] := newmat[i]*cs[i];
3610				od;
3611				return ProjElWithFrob(newmat,frob2,f);
3612			end;
3613
3614		else
3615
3616			ch := BaseChangeToCanonical( SesquilinearForm(h) );
3617			chinv := ch^-1;
3618
3619			twinerfunc := function(g)
3620				local mat,newmat,frob,newmat2,n,frob2,j,arg;
3621				frob := g!.frob;
3622				mat := ch * Unpack(g!.mat) * chinv^(frob^-1);
3623				newmat := [];
3624				newmat[1] := PluckerCoordinates([mat[2],mat[1]]);
3625				newmat[2] := PluckerCoordinates([mat[3],mat[1]]);
3626				newmat[3] := PluckerCoordinates([mat[4],mat[1]]);
3627				newmat[4] := PluckerCoordinates([mat[3],mat[2]]);
3628				newmat[5] := PluckerCoordinates([mat[4],-mat[2]]);
3629				newmat[6] := -PluckerCoordinates([-mat[4],mat[3]]);
3630				newmat2 :=  xinv*newmat*x^(frob^-1);
3631				n := First(newmat2[1],x->not IsZero(x));
3632				newmat2 := newmat2/n;
3633				if not IsOne(frob) then
3634					j := Log(frob!.power,Characteristic(f));
3635				else
3636					j := 0;
3637				fi;
3638				frob2 := FrobeniusAutomorphism(GF(q))^(j mod q);
3639				arg := ShallowCopy(cq5q * newmat2 * cq5qinv^(frob2^-1));
3640				ConvertToMatrixRep(arg);
3641				return ProjElWithFrob(arg,frob2,GF(q));
3642			end;
3643
3644			twinerprefun := function( g )
3645				local mat, newmat, lines, pts, ipts, ilines, e, ept,
3646					ielines, ie, cs, iept, frob, frob2, j, n, switch;
3647				frob := g!.frob;
3648				#first setup the frobenius automorphism over GF(q^2)=f.
3649				if not IsOne(frob) then
3650					j := Log(frob!.power,Characteristic(f));
3651				else
3652					j := 0;
3653				fi;
3654				frob2 := FrobeniusAutomorphism(f)^j;
3655				#base change. Note that frob is used to change from different Q-(5,q)'s, frob2 for Q-(5,q) -> Q+(5,q^2)
3656				mat := x * (cq5qinv * Unpack(g!.mat) * cq5q^(frob^-1) ) * xinv^(frob2^-1);      #base change is here.
3657				#now from Q+(5,q^2) to H(3,q^2).
3658				lines:= [];
3659				pts := [];
3660				ipts := [];
3661				ilines := [];
3662				lines[1] := [[id[1],id[2]],[id[1],id[3]],[id[1],id[4]]];
3663				pts[1] := List(lines[1],y->PluckerCoordinates(y));
3664				ipts[1] := (pts[1]*mat);
3665				ilines[1] := List(ipts[1],y->InversePluckerCoordinates(y));
3666				switch := theta^0;
3667				if Rank(Union(ilines[1])) = 3 then
3668					#Print("switch\n");
3669					mat := (mat^theta) * special;
3670					frob2 := frob2 * theta;
3671					switch := theta;
3672				fi;
3673				n := First(mat[1],x->not IsZero(x));
3674				mat := mat/n;
3675				lines[1] := [[id[1],id[2]],[id[1],id[3]]];
3676				lines[2] := [[id[2],id[3]],[id[2],id[4]]];
3677				lines[3] := [[id[3],id[4]],[id[3],id[1]]];
3678				lines[4] := [[id[4],id[1]],[id[4],id[2]]];
3679				pts := List(lines,x->List(x,y->PluckerCoordinates(y)));
3680				ipts := List(pts,x->(x*mat)^switch);
3681				#ipts := List(pts,x->(x*mat));
3682				ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
3683				ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
3684				newmat := List(ipts,x->x[1]);
3685				e := [1,1,1,1]*one;
3686				ept := List([[e,id[1]],[e,id[2]]],y->PluckerCoordinates(y));
3687				iept := List(ept,x->(x*mat)^switch);
3688				#iept := List(ept,x->(x*mat));
3689				ielines := List(iept,x->InversePluckerCoordinates(x));
3690				ie := SumIntersectionMat(ielines[1],ielines[2])[2];
3691				cs := SolutionMat(newmat,ie[1]);
3692				for i in [1..4] do
3693					newmat[i] := newmat[i]*cs[i];
3694				od;
3695				return ProjElWithFrob(chinv * newmat * ch^(frob2^-1),frob2,f);
3696			end;
3697
3698		fi;
3699		if not reverse then
3700			data.twinerfunc := twinerfunc;
3701			data.twinerprefun := twinerprefun;
3702		else
3703			data.twinerfunc := twinerprefun;
3704			data.twinerprefun := twinerfunc;
3705		fi;
3706    fi;
3707
3708	return data;
3709
3710end );
3711
3712# Added april 2014 jdb.
3713#############################################################################
3714#O  NaturalDuality( <gq1>, <gq2>, <bool> )
3715# This is the interface to the helper functions. It simply checks the input
3716# and decides which NaturalDuality... to use.
3717##
3718InstallMethod( NaturalDuality,
3719	"for a GQ and a GQ",
3720	[ IsClassicalGQ, IsClassicalGQ, IsBool ],
3721	function( gq1, gq2, computeintertwiner )
3722	local bf1,bf2,data, coll1,coll2,gens1,gens2, map, hom;
3723	bf1 := gq1!.basefield;
3724	bf2 := gq2!.basefield;
3725    if IsSymplecticSpace( gq1 ) and IsParabolicQuadric( gq2 ) then
3726        if bf1 <> bf2 then
3727			Error("<gq1> and <gq2> have a different base field");
3728		fi;
3729		data := NaturalDualitySymplectic( gq1, gq2, computeintertwiner, false);
3730	elif (IsHermitianPolarSpace( gq1 ) and Dimension( gq1 ) = 3) and IsEllipticQuadric( gq2) then
3731		if Size(bf1) <> Size(bf2)^2 then
3732			Error("base field of <gq1> must have order the square of the base field of <gq2>");
3733		fi;
3734		data := NaturalDualityHermitian( gq1, gq2, computeintertwiner, false);
3735    elif IsParabolicQuadric( gq1 ) and IsSymplecticSpace( gq2 ) then
3736        if bf1 <> bf2 then
3737			Error("<gq1> and <gq2> have a different base field");
3738		fi;
3739		data := NaturalDualitySymplectic( gq2, gq1, computeintertwiner, true);
3740	elif IsEllipticQuadric( gq1 ) and (IsHermitianPolarSpace( gq2 ) and Dimension( gq2 ) = 3) then
3741		if Size(bf1^2) <> Size(bf2) then
3742			Error("base field of <gq2> must have order the square of the base field of <gq1>");
3743		fi;
3744		data := NaturalDualityHermitian( gq2, gq1, computeintertwiner, true);
3745	else
3746        Error("no duality possible between <gq1> and <gq2>");
3747    fi;
3748	map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(gq1), ElementsOfIncidenceStructure(gq2),
3749					data.func, data.pre);
3750	SetIsBijective( map, true );
3751    if computeintertwiner then
3752        if (HasIsCanonicalPolarSpace( gq1 ) and IsCanonicalPolarSpace( gq1 )) or
3753            HasCollineationGroup( gq1 ) then
3754			coll1 := CollineationGroup(gq1);
3755			gens1 := GeneratorsOfGroup( coll1 );
3756			gens2 := List(gens1, data.twinerfunc);
3757			coll2 := GroupWithGenerators(gens2);
3758			hom := GroupHomomorphismByFunction(coll1, coll2, data.twinerfunc, data.twinerprefun);
3759			SetIntertwiner( map, hom );
3760			UseIsomorphismRelation(coll1,coll2);
3761        elif (HasIsCanonicalPolarSpace( gq2 ) and IsCanonicalPolarSpace( gq2 )) or
3762			HasCollineationGroup( gq2 ) then
3763			coll2 := CollineationGroup( gq2 );
3764			gens2 := GeneratorsOfGroup( coll2 );
3765			gens1 := List(gens2, data.twinerprefun );
3766			coll1 := GroupWithGenerators(gens1);
3767			hom := GroupHomomorphismByFunction(coll1, coll2, data.twinerfunc, data.twinerprefun);
3768			SetIntertwiner( map, hom );
3769			UseIsomorphismRelation(coll1,coll2);
3770		else
3771			Info(InfoFinInG, 1, "No intertwiner computed. One of the polar spaces must have a collineation group computed");
3772		fi;
3773    fi;
3774
3775	return map;
3776end );
3777
3778# Added may 2014 jdb.
3779#############################################################################
3780#O  NaturalDuality( <gq1>, <gq2> )
3781# returns NaturalDuality( <gq1>, <gq2>, true)
3782##
3783InstallMethod( NaturalDuality,
3784	"for a GQ and a GQ",
3785	[ IsClassicalGQ, IsClassicalGQ ],
3786	function(gq1,gq2)
3787		return NaturalDuality(gq1,gq2,true);
3788	end);
3789
3790# Added may 2014 jdb.
3791#############################################################################
3792#O  NaturalDuality( <gq1>, <computeintertwiner> )
3793# This is the interface to the helper functions. It simply checks the input
3794# and decides which NaturalDuality... to use.
3795##
3796InstallMethod( NaturalDuality,
3797	"for a GQ and a GQ",
3798	[ IsClassicalGQ, IsBool ],
3799	function( gq1, computeintertwiner )
3800	local q;
3801    if IsSymplecticSpace( gq1 ) then
3802        return NaturalDuality( gq1, ParabolicQuadric(4, BaseField(gq1)), computeintertwiner ) ;
3803    elif (IsHermitianPolarSpace( gq1 ) and Dimension( gq1 ) = 3) then
3804        q := Sqrt(Size(BaseField(gq1)));
3805		return NaturalDuality( gq1, EllipticQuadric(5,GF(q)), computeintertwiner );
3806    elif IsParabolicQuadric( gq1 ) then
3807		return NaturalDuality( gq1, SymplecticSpace(3, BaseField(gq1) ),  computeintertwiner );
3808	elif IsEllipticQuadric( gq1 ) then
3809		q := Size(BaseField(gq1));
3810		return NaturalDuality( gq1, HermitianPolarSpace(3, q^2), computeintertwiner);
3811	else
3812        Error("no duality possible on <gq1>");
3813    fi;
3814    end );
3815
3816# Added may 2014 jdb.
3817#############################################################################
3818#O  NaturalDuality( <gq1> )
3819# returns NaturalDuality( <gq1>, true)
3820##
3821InstallMethod( NaturalDuality,
3822	"for a GQ and a GQ",
3823	[ IsClassicalGQ ],
3824	function(gq1)
3825		return NaturalDuality(gq1,true);
3826	end);
3827
3828#############################################################################
3829#
3830# Q(2n,q) -> W(2n-1,q) (q even).
3831#
3832#############################################################################
3833
3834# Added april 2014 jdb.
3835#############################################################################
3836#O  IsomorphismPolarSpacesProjectionFromNucleus( <quadric>, <w>, <bool> )
3837# returns the isomorphism between Q(2n,q) and W(2n-1,q). Both may be user
3838# defined.
3839# standard Q(2n,q): X0^2+X1X2+...X2n-1X2n: nucleus: (1,0,0,...,0,0)
3840# standard W(2n,q): X0Y1+X1Y0+...X2n-2X2n.
3841# projecting from nucleus yields the automorphism. Going back: consider a point
3842# of W(2n-1,q): evaluate it under X1X2+...X2n-1X2n: this yields a value z.
3843# the equation X0^2 = z has exactly one solution -> point of Q(2n,q).
3844# The interwiner: given an element of PGammaO(2n+1,q): it is sufficient to select
3845# a submatrix, sinc the projection is equivalent with removing X0
3846# given an element of PGammaSp: the points (1,0,...0), (0,1,...0), ... (0,0,...0)
3847# are, with an extra 0 in front, also points of Q(2n,q). Compute the elements of
3848# the symplectic points, compute the preimage of this image under the projection
3849# this yields the first column of the matrix of the corresponding element of
3850# PGammaO. Since the nucleus is fixed under the elements of PGammaO, the first
3851# row (except the very first element), is zero. To compute the first element,
3852# we need the image of the n+2 nd point under the given element. So find a
3853# symplectic point that together with the other symplectic points forms part of a
3854# frame, make sure the preimage can be computed, compute the image under el, and
3855# compute its preimage. This gives the very first element of the PGammaO element.
3856# Note that for q=2 this element is necessarily one. Note that the general code
3857# works anyway also for q=2 and n even, which explains the exception in the
3858# twinerprefun.
3859# Finally: base changes are included if Q and W are not canonical. This is straight-
3860# forward.
3861##
3862InstallMethod( IsomorphismPolarSpacesProjectionFromNucleus,
3863	"for two classical polar spaces",
3864	[ IsClassicalPolarSpace, IsClassicalPolarSpace, IsBool ],
3865	function(quadric, w, computeintertwiner)
3866	local q,f, func, pre, nucleus, n, mat, hyp, map, can_form, cquadric, cquadricinv,
3867	cw, cwinv, twinerfunc, twinerprefun, coll1, coll2, hom, ones, one, gens1, gens2;
3868	f := BaseField(quadric);
3869	q := Size(f);
3870    one := One(f);
3871	if not IsEvenInt(q) and f = BaseField(w) then
3872		Error(" Characteristic of basefields must be even and basfields must be equal");
3873	elif Rank(quadric) <> Rank(w) then
3874		Error(" Rank of polar spaces must be equal ");
3875	fi;
3876	n := quadric!.dimension + 1;
3877	mat := IdentityMat(n,f);
3878	if IsCanonicalPolarSpace(quadric) then
3879		can_form := QuadraticForm(quadric);
3880		cquadric := IdentityMat(n,f);
3881		cquadricinv := cquadric;
3882	else
3883		cquadric := BaseChangeToCanonical(QuadraticForm(quadric));
3884		can_form := QuadraticFormByMatrix( CanonicalQuadraticForm("parabolic", n, f), f);
3885		cquadricinv := cquadric^-1;
3886	fi;
3887	if IsCanonicalPolarSpace(w) then
3888		cw := IdentityMat(n-1,f);
3889		cwinv := cw;
3890	else
3891		cw := BaseChangeToCanonical(SesquilinearForm(w));
3892		cwinv := cw^-1;
3893	fi;
3894	nucleus := mat[1]; #vector representing the nucleus
3895	hyp := mat{[2..n]}; #hyperplane on which we will project.
3896	func := function( el )
3897		local vec, proj;
3898		if el!.type = 1 then
3899			vec := [Unpack(el!.obj) * cquadricinv, nucleus];
3900		else
3901			vec := Concatenation(Unpack(el!.obj) * cquadricinv, [nucleus]);
3902		fi;
3903		proj := SumIntersectionMat(vec, hyp)[2]; #hard coded Meet operation :-)
3904		vec := List(proj,x->x{[2..n]}); #make it a GF(q)^(n-1) vector ends the projection.
3905		return VectorSpaceToElement(w, vec * cw);
3906	end;
3907	pre := function(el)
3908		local vec, i;
3909		if el!.type = 1 then
3910			vec := [Unpack(el!.obj) * cwinv];
3911		else
3912			vec := Unpack(el!.obj) * cwinv;
3913		fi;
3914		vec := List(vec,x->Concatenation([0*Z(q)^0],x));
3915		for i in [1..Length(vec)] do
3916			vec[i][1] := (vec[i]^can_form)^(q/2);
3917		od;
3918		return VectorSpaceToElement(quadric, vec * cquadric);
3919	end;
3920
3921    ones := List([1..n],x->one); #all ones for several purposes.
3922
3923	twinerfunc := function( el )
3924		local mat,frob;
3925		frob := el!.frob;
3926		#mat := Unpack(el!.mat){[2..n]}{[2..n]};
3927		mat := cquadric * Unpack(el!.mat) * cquadricinv^(frob^-1);
3928		mat := mat{[2..n]}{[2..n]};
3929		return ProjElWithFrob(cwinv * mat * cw^(frob^-1),frob,f);
3930	end;
3931
3932	twinerprefun := function( el )
3933		local newmat,mat,frob,i,vec,y,newvec,z,zprime;
3934		frob := el!.frob;
3935		mat := cw * Unpack(el!.mat) * cwinv^(frob^-1);
3936		#mat := Unpack(el!.mat);
3937		newmat := NullMat(n,n,f);
3938		newmat{[2..n]}{[2..n]} := mat;
3939
3940        #compute the first column of newmat (except for newmat[1][1].
3941		for i in [2..n] do
3942			vec := Concatenation([0*Z(q)^0],mat[i-1])^frob;
3943			newmat[i][1] := ((vec^can_form)^(q/2))^(frob^-1);
3944		od;
3945		#vec := List(TransposedMat(mat),x->Sum(x)^frob); #is the image of (1,1,...,1) under el.
3946
3947        if not (q=2 and IsEvenInt((n-1)/2)) then
3948            vec := ShallowCopy(ones);
3949            vec[1] := 0*one;
3950            vec[2] := Z(q);
3951            z := (vec^can_form)^(q/2);
3952
3953            y := vec*newmat;
3954            y := y[1];
3955            newvec := (vec * newmat)^frob; #remind that the symplectic part is in [2..n].
3956            newvec[1] := 0*one;
3957            zprime := ((newvec^can_form)^(q/2))^(frob^-1);
3958
3959        #image of (alpha,1,1,...,1) under el (symplectic point!)
3960
3961            newmat[1][1] := (zprime+y)/z;
3962        else
3963            newmat[1][1] := one;
3964        fi;
3965
3966		return ProjElWithFrob(cquadricinv * newmat * cquadric^(frob^-1), frob, f);
3967		#return ProjElWithFrob( newmat , frob, f);
3968	end;
3969
3970    map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(quadric), ElementsOfIncidenceStructure(w), func, pre);
3971    SetIsBijective( map, true );
3972
3973    if computeintertwiner then
3974        if (HasIsCanonicalPolarSpace( quadric ) and IsCanonicalPolarSpace( quadric )) or
3975            HasCollineationGroup( quadric ) then
3976			coll1 := CollineationGroup(quadric);
3977			gens1 := GeneratorsOfGroup( coll1 );
3978			gens2 := List(gens1, twinerfunc);
3979			coll2 := GroupWithGenerators(gens2);
3980			hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
3981			SetIntertwiner( map, hom );
3982			UseIsomorphismRelation(coll1,coll2);
3983        elif (HasIsCanonicalPolarSpace( w ) and IsCanonicalPolarSpace( w )) or
3984			HasCollineationGroup( w ) then
3985			coll2 := CollineationGroup( w );
3986			gens2 := GeneratorsOfGroup( coll2 );
3987			gens1 := List(gens2, twinerprefun );
3988			coll1 := GroupWithGenerators(gens1);
3989			hom := GroupHomomorphismByFunction(coll1, coll2, twinerfunc, twinerprefun);
3990			SetIntertwiner( map, hom );
3991			UseIsomorphismRelation(coll1,coll2);
3992		else
3993			Info(InfoFinInG, 1, "No intertwiner computed. One of the polar spaces must have a collineation group computed");
3994		fi;
3995    fi;
3996
3997    return map;
3998 end );
3999
4000# Added july 2014 jdb.
4001#############################################################################
4002#O SelfDualitySymplectic
4003# combines NaturalDualitySymplectic with IsomorphismPolarSpacesProjectionFromNucleus,
4004# most of the code is recycled from these operations, and is documented there.
4005# Note that we have less base changes to deal with. Starting from a canonical W(3,q),
4006# the obtained Q(4,q) has form X_0^2+X1X4+X2X3=0. The base change X4<->X2 is represented
4007# by perm5 at the Q(4,q) side and perm4 at the projected W(3,q) side.
4008# This operation is a helper operation for SelfDuality, there are no checks
4009# on the input.
4010# order: W(3,q) -> Q(4,q) -> W(3,q), by Plucker and then projection.
4011# -> twinerfunc: 1. PsP(4,q) -> PGO(5,q) by Plucker (twinerfunc of NaturalDualitySymplectic)
4012#                2. PGO(5,q) -> PsP(4,q) by projection (twinerfunc of Isomorphism...Nucleus)
4013# -> twinerprefun: 1. PsP(4,q) -> PPGO(5,q) by inverse projection (twinerprefun of Isomorphism...Nucleus)
4014#                  2. PGO(5,q) -> PsP(4,q) by inverse Plucker (twinerprefun of NaturalDualitySymplectic)
4015#############################################################################
4016#
4017InstallMethod( SelfDualitySymplectic,
4018	"for a symplectic GQ and a boolean",
4019	[ IsClassicalGQ, IsBool ],
4020	function( w, computeintertwiner )
4021    local f, one, mat, quad_form, quadric, data, form_w, cw, cwinv, q, map,
4022        func, pre, formw, delta, hyp, coll1, hom, ones, can_form,
4023		twinerfunc, twinerprefun, cq4qinv, id, hyp2, nucleus, perm4, gram, perm5;
4024    f := w!.basefield;
4025    one := One(f);
4026	formw := SesquilinearForm( w );
4027    delta := PolarityOfProjectiveSpace(w);
4028    hyp := IdentityMat(6,f){[1..5]}; #this and next are in fact HyperplaneByDualCoorindates([1,0,0,0,0,1]..)
4029    hyp[1][6] := -one;
4030    nucleus := [1,0,0,0,0]*one;
4031	mat := IdentityMat(5,f);
4032    hyp2 := mat{[2..5]};
4033    perm4 := IdentityMat(4,f){[1,4,3,2]};
4034    perm5 := IdentityMat(5,f){[1,2,5,4,3]};
4035    gram := [[1,0,0,0,0],[0,0,0,0,1],[0,0,0,1,0],[0,0,0,0,0],[0,0,0,0,0]]*one;
4036    quad_form := QuadraticFormByMatrix(gram,f);
4037    q := Size(f);
4038    cw := IdentityMat(4,f);
4039    if not IsCanonicalPolarSpace( w ) then
4040        cw := BaseChangeToCanonical( formw );
4041    fi;
4042    cwinv := cw^-1;
4043    can_form := QuadraticFormByMatrix( CanonicalQuadraticForm("parabolic", 5, f), f);
4044    ones := List([1..5],x->one); #all ones for several purposes.
4045    id := IdentityMat(4, f);
4046
4047    func := function( el )
4048        local list,plane,vec,proj;
4049        if el!.type = 2 then #dealing with a line
4050            vec := [ PluckerCoordinates(Unpack(el!.obj) * cwinv){[1..5]}, nucleus];
4051            proj := SumIntersectionMat(vec, hyp2)[2];
4052            vec := List(proj,x->x{[2..5]});
4053            return VectorSpaceToElement(w, ( vec * perm4 ) * cw);
4054        else
4055            plane := el^delta;
4056            vec := Unpack(plane!.obj) * cwinv;
4057            list := [ PluckerCoordinates(vec{[1,2]}), PluckerCoordinates(vec{[2,3]}), PluckerCoordinates(vec{[1,3]}) ];
4058            plane := SumIntersectionMat(list, hyp)[2]; #hard coded Meet operation :-)
4059            list := List(plane,x->x{[1..5]});
4060            vec := Concatenation(list, [nucleus]);
4061            proj := SumIntersectionMat(vec, hyp2)[2];
4062            vec := List(proj,x->x{[2..5]});
4063            return VectorSpaceToElement(w, ( vec * perm4 ) * cw);
4064        fi;
4065    end;
4066
4067    pre := function( el )
4068        local vec,i;
4069        if el!.type = 1 then #dealing with a point
4070            vec := [Unpack(el!.obj) * cwinv] * perm4;
4071        else
4072            vec := (Unpack(el!.obj) * cwinv )* perm4;
4073        fi;
4074        vec := List(vec,x->Concatenation([0*Z(q)^0],x));
4075        for i in [1..Length(vec)] do
4076            vec[i][1] := (vec[i]^quad_form)^(q/2);
4077        od;
4078        if el!.type = 1 then #dealing with a point
4079            vec[1][6] := -vec[1][1];
4080            return VectorSpaceToElement(w, InversePluckerCoordinates(vec[1]) * cw );
4081        else
4082            vec[1][6] := -vec[1][1];
4083            vec[2][6] := -vec[2][1];
4084            return VectorSpaceToElement(w, (SumIntersectionMat(InversePluckerCoordinates(vec[1]), InversePluckerCoordinates(vec[2]))[2]) * cw );
4085        fi;
4086    end;
4087
4088    map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(w), ElementsOfIncidenceStructure(w), func, pre);
4089    SetIsBijective( map, true );
4090
4091    twinerfunc := function(g)
4092        local mat,newmat,frob;
4093        frob := g!.frob;
4094        mat := cw * Unpack(g!.mat) * cwinv^(frob^-1);
4095        newmat := [];
4096        newmat[2] := PluckerCoordinates([mat[3],mat[1]]){[1..5]};
4097        newmat[3] := PluckerCoordinates([mat[4],mat[1]]){[1..5]};
4098        newmat[4] := -PluckerCoordinates([-mat[3],mat[2]]){[1..5]};
4099        newmat[5] := PluckerCoordinates([mat[4],-mat[2]]){[1..5]};
4100        newmat[1] := PluckerCoordinates([mat[2]+mat[3],mat[1]+mat[4]]){[1..5]} - newmat[2] - newmat[5];
4101        newmat := perm5 * newmat * perm5^(frob^-1);
4102        newmat := newmat{[2..5]}{[2..5]};
4103        return ProjElWithFrob(cwinv * newmat * cw^(frob^-1),frob,f);
4104    end;
4105
4106	twinerprefun := function( el )
4107		local newmat,mat,frob,i,vec,y,newvec,z,zprime,
4108            lines, pts, ipts, ilines, e, x, ept, ielines,
4109            ie, cs, iept, j;
4110		frob := el!.frob;
4111		mat := cw * Unpack(el!.mat) * cwinv^(frob^-1);
4112		newmat := NullMat(5,5,f);
4113		newmat{[2..5]}{[2..5]} := mat;
4114
4115        #compute the first column of newmat (except for newmat[1][1].
4116		for i in [2..5] do
4117			vec := Concatenation([0*Z(q)^0],mat[i-1])^frob;
4118			newmat[i][1] := ((vec^can_form)^(q/2))^(frob^-1);
4119		od;
4120
4121        if not q = 2 then
4122            vec := ShallowCopy(ones);
4123            vec[1] := 0*one;
4124            vec[2] := Z(q);
4125            z := (vec^can_form)^(q/2);
4126
4127            y := vec*newmat;
4128            y := y[1];
4129            newvec := (vec * newmat)^frob; #remind that the symplectic part is in [2..n].
4130            newvec[1] := 0*one;
4131            zprime := ((newvec^can_form)^(q/2))^(frob^-1);
4132
4133        #image of (alpha,1,1,...,1) under el (symplectic point!)
4134
4135            newmat[1][1] := (zprime+y)/z;
4136        else
4137            newmat[1][1] := one;
4138        fi;
4139
4140        mat := perm5 * newmat * perm5;
4141
4142        lines:= [];
4143        lines[1] := [[id[1],id[3]],[id[1],id[4]]];
4144        lines[2] := [[id[2],id[3]],[id[2],id[4]]];
4145        lines[3] := [[id[3],id[1]],[id[3],id[2]]];
4146        lines[4] := [[id[4],id[1]],[id[4],id[2]]];
4147        pts := List(lines,x->List(x,y->PluckerCoordinates(y){[1..5]}));
4148        ipts := List(pts,x->x*mat);
4149        for i in [1..Length(ipts)] do
4150            for j in [1..2] do
4151                ipts[i][j][6] := -ipts[i][j][1];
4152            od;
4153        od;
4154        ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
4155        ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
4156        newmat := List(ipts,x->x[1]);
4157        e := [1,1,1,1]*one;
4158        ept := List([[e,id[1]+id[4]],[e,id[1]+id[2]]],y->PluckerCoordinates(y){[1..5]});
4159        iept := List(ept,x->x*mat);
4160        iept[1][6] := -iept[1][1];
4161        iept[2][6] := -iept[2][1];
4162
4163        ielines := List(iept,x->InversePluckerCoordinates(x));
4164        ie := SumIntersectionMat(ielines[1],ielines[2])[2];
4165        cs := SolutionMat(newmat,ie[1]);
4166        for i in [1..4] do
4167            newmat[i] := newmat[i]*cs[i];
4168        od;
4169        return ProjElWithFrob(cwinv * newmat * cw^(frob^-1),frob,f);
4170
4171	end;
4172
4173    if computeintertwiner then
4174        if (HasIsCanonicalPolarSpace( w ) and IsCanonicalPolarSpace( w )) or
4175            HasCollineationGroup( w ) then
4176			coll1 := CollineationGroup(w);
4177			hom := GroupHomomorphismByFunction(coll1, coll1, twinerfunc, twinerprefun);
4178			SetIntertwiner( map, hom );
4179			#UseIsomorphismRelation(coll1,coll2);
4180 		else
4181			Info(InfoFinInG, 1, "No intertwiner computed. The polar space must have a collineation group computed");
4182		fi;
4183    fi;
4184
4185    return map;
4186end );
4187
4188# Added july 2014 jdb.
4189#############################################################################
4190#O SelfDualityParabolic
4191# combines NaturalDualitySymplectic with IsomorphismPolarSpacesProjectionFromNucleus,
4192# most of the code is recycled from these operations, and is documented there.
4193# Note that we have less base changes to deal with. Starting from a canonical W(3,q),
4194# the obtained Q(4,q) has form X_0^2+X1X4+X2X3=0. The base change X4<->X2 is represented
4195# by perm5 at the Q(4,q) side and perm4 at the projected W(3,q) side.
4196# This operation is a helper operation for SelfDuality, there are no checks
4197# on the input.
4198# order: Q(4,q) -> W(3,q) -> Q(4,q), by projection and then Plucker.
4199# -> twinerfunc: 1. PGO(5,q) -> PsP(4,q) by projection (twinerfunc of Isomorphism...Nucleus)
4200#                2. PsP(4,q) -> PGO(5,q) by Plucker (twinerfunc of NaturalDualitySymplectic)
4201# -> twinerprefun: 1. PGO(5,q) -> PsP(4,q) by inverse Plucker (twinerprefun of NaturalDualitySymplectic)
4202#                  2. PsP(4,q) -> PPGO(5,q) by inverse projection (twinerprefun of Isomorphism...Nucleus)
4203#############################################################################
4204#
4205InstallMethod( SelfDualityParabolic,
4206	"for a symplectic GQ and a boolean",
4207	[ IsClassicalGQ, IsBool ],
4208	function( q4q, computeintertwiner )
4209    local f, one, mat, quad_form, quadric, data, cq, cqinv, q, map, can_form,
4210        func, pre, formq, delta, hyp, coll1, coll2, gens1, gens2, hom, ones,
4211		twinerfunc, twinerprefun, id, hyp2, nucleus, perm4, perm5, gram;
4212
4213    f := q4q!.basefield;
4214    one := One(f);
4215    hyp := IdentityMat(6,f){[1..5]}; #this and next are in fact HyperplaneByDualCoorindates([1,0,0,0,0,1]..)
4216    hyp[1][6] := -one;
4217    mat := IdentityMat(5,f);
4218    hyp2 := mat{[2..5]};
4219    perm4 := IdentityMat(4,f){[1,4,3,2]};
4220    perm5 := IdentityMat(5,f){[1,2,5,4,3]};
4221    quad_form := QuadraticFormByMatrix(CanonicalQuadraticForm("parabolic",5,f),f);
4222    q := Size(f);
4223    nucleus := [1,0,0,0,0]*one;
4224    delta := CanonicalGramMatrix("symplectic",4,f); #we will hard code the polarity :-)
4225    formq := QuadraticForm(q4q);
4226
4227    cq := IdentityMat(5,f);
4228    if not IsCanonicalPolarSpace( q4q ) then
4229        cq := BaseChangeToCanonical( formq );
4230    fi;
4231    cqinv := cq^-1;
4232
4233    func := function( el )
4234        local list,plane,vec,proj;
4235        if el!.type = 1 then
4236            vec := [Unpack(el!.obj) * cqinv, nucleus];
4237        else
4238            vec := Concatenation(Unpack(el!.obj) * cqinv, [nucleus]);
4239        fi;
4240        proj := SumIntersectionMat(vec, hyp2)[2]; #hard coded Meet operation :-)
4241        vec := List(proj,x->x{[2..5]}); #make it a GF(q)^(n-1) vector ends the projection.
4242        if el!.type = 2 then
4243            vec := [PluckerCoordinates(vec){[1..5]}] * perm5;
4244            return VectorSpaceToElement(q4q,vec * cq);
4245        else
4246            plane := NullspaceMat(TransposedMat(vec*delta));
4247            list := [ PluckerCoordinates(plane{[1,2]}), PluckerCoordinates(plane{[2,3]}), PluckerCoordinates(plane{[1,3]}) ];
4248            plane := SumIntersectionMat(list, hyp)[2]; #hard coded Meet operation :-)
4249            list := List(plane,x->x{[1..5]}) * perm5;
4250            return VectorSpaceToElement(q4q,list * cq);
4251        fi;
4252    end;
4253
4254    pre := function( el )
4255        local vec,ivec,i;
4256        if el!.type = 1 then #dealing with a point
4257            vec := [Unpack(el!.obj) * cqinv ] * perm5;
4258            vec[1][6] := -vec[1][1];
4259            ivec := InversePluckerCoordinates(vec[1]);
4260        else
4261            vec := (Unpack(el!.obj) * cqinv )* perm5;
4262            vec[1][6] := -vec[1][1];
4263            vec[2][6] := -vec[2][1];
4264            ivec := SumIntersectionMat(InversePluckerCoordinates(vec[1]), InversePluckerCoordinates(vec[2]))[2];
4265        fi;
4266        ivec := List(ivec,x->Concatenation([0*Z(q)^0],x));
4267        for i in [1..Length(ivec)] do
4268            ivec[i][1] := (ivec[i]^quad_form)^(q/2);
4269        od;
4270        return VectorSpaceToElement(q4q,ivec * cq);
4271    end;
4272
4273    map := GeometryMorphismByFunction(ElementsOfIncidenceStructure(q4q), ElementsOfIncidenceStructure(q4q), func, pre);
4274    SetIsBijective( map, true );
4275
4276    can_form := QuadraticFormByMatrix( CanonicalQuadraticForm("parabolic", 5, f), f);
4277    ones := List([1..5],x->one); #all ones for several purposes.
4278    id := IdentityMat(4, f);
4279
4280	twinerfunc := function( el )
4281		local mat,frob, newmat;
4282		frob := el!.frob;
4283		mat := cq * Unpack(el!.mat) * cqinv^(frob^-1);
4284		mat := mat{[2..5]}{[2..5]};
4285        newmat := [];
4286        newmat[2] := PluckerCoordinates([mat[3],mat[1]]){[1..5]};
4287        newmat[3] := PluckerCoordinates([mat[4],mat[1]]){[1..5]};
4288        newmat[4] := -PluckerCoordinates([-mat[3],mat[2]]){[1..5]};
4289        newmat[5] := PluckerCoordinates([mat[4],-mat[2]]){[1..5]};
4290        newmat[1] := PluckerCoordinates([mat[2]+mat[3],mat[1]+mat[4]]){[1..5]} - newmat[2] - newmat[5];
4291        newmat := perm5 * newmat * perm5;
4292        return ProjElWithFrob(cqinv * newmat * cq^(frob^-1),frob,f);
4293	end;
4294
4295	twinerprefun := function( el )
4296		local newmat,mat,frob,i,vec,y,newvec,z,zprime,
4297            lines, pts, ipts, ilines, e, x, ept, ielines,
4298            ie, cs, iept, j;
4299		frob := el!.frob;
4300		mat := perm5 * cq * Unpack(el!.mat) * cqinv^(frob^-1) * perm5;
4301
4302        lines:= [];
4303        lines[1] := [[id[1],id[3]],[id[1],id[4]]];
4304        lines[2] := [[id[2],id[3]],[id[2],id[4]]];
4305        lines[3] := [[id[3],id[1]],[id[3],id[2]]];
4306        lines[4] := [[id[4],id[1]],[id[4],id[2]]];
4307        pts := List(lines,x->List(x,y->PluckerCoordinates(y){[1..5]}));
4308        ipts := List(pts,x->x*mat);
4309        for i in [1..Length(ipts)] do
4310            for j in [1..2] do
4311                ipts[i][j][6] := -ipts[i][j][1];
4312            od;
4313        od;
4314        ilines := List(ipts,x->List(x,y->InversePluckerCoordinates(y)));
4315        ipts := List(ilines, x->SumIntersectionMat(x[1],x[2])[2]);
4316        newmat := List(ipts,x->x[1]);
4317        e := [1,1,1,1]*one;
4318        ept := List([[e,id[1]+id[4]],[e,id[1]+id[2]]],y->PluckerCoordinates(y){[1..5]});
4319        iept := List(ept,x->x*mat);
4320        iept[1][6] := -iept[1][1];
4321        iept[2][6] := -iept[2][1];
4322
4323        ielines := List(iept,x->InversePluckerCoordinates(x));
4324        ie := SumIntersectionMat(ielines[1],ielines[2])[2];
4325        cs := SolutionMat(newmat,ie[1]);
4326        for i in [1..4] do
4327            newmat[i] := newmat[i]*cs[i];
4328        od;
4329        mat := ShallowCopy(newmat);
4330
4331		newmat := NullMat(5,5,f);
4332		newmat{[2..5]}{[2..5]} := mat;
4333
4334        #compute the first column of newmat (except for newmat[1][1].
4335		for i in [2..5] do
4336			vec := Concatenation([0*Z(q)^0],mat[i-1])^frob;
4337			newmat[i][1] := ((vec^can_form)^(q/2))^(frob^-1);
4338		od;
4339
4340        if not q = 2 then
4341            vec := ShallowCopy(ones);
4342            vec[1] := 0*one;
4343            vec[2] := Z(q);
4344            z := (vec^can_form)^(q/2);
4345
4346            y := vec*newmat;
4347            y := y[1];
4348            newvec := (vec * newmat)^frob; #remind that the symplectic part is in [2..n].
4349            newvec[1] := 0*one;
4350            zprime := ((newvec^can_form)^(q/2))^(frob^-1);
4351
4352        #image of (alpha,1,1,...,1) under el (symplectic point!)
4353
4354            newmat[1][1] := (zprime+y)/z;
4355        else
4356            newmat[1][1] := one;
4357        fi;
4358
4359        return ProjElWithFrob(cqinv * newmat * cq^(frob^-1),frob,f);
4360
4361	end;
4362
4363    if computeintertwiner then
4364        if (HasIsCanonicalPolarSpace( q4q ) and IsCanonicalPolarSpace( q4q )) or
4365            HasCollineationGroup( q4q ) then
4366			coll1 := CollineationGroup(q4q);
4367			hom := GroupHomomorphismByFunction(coll1, coll1, twinerfunc, twinerprefun);
4368			SetIntertwiner( map, hom );
4369			#UseIsomorphismRelation(coll1,coll2);
4370 		else
4371			Info(InfoFinInG, 1, "No intertwiner computed. The polar space must have a collineation group computed");
4372		fi;
4373    fi;
4374
4375    return map;
4376end );
4377
4378# Added july 2014 jdb.
4379#############################################################################
4380#O  SelfDuality( <gq>, <bool> )
4381# This is the interface to the helper functions. It simply checks the input
4382# and decides which NaturalDuality... to use.
4383##
4384InstallMethod( SelfDuality,
4385	"for a GQ and a boolean",
4386	[ IsClassicalGQ, IsBool ],
4387	function( gq, computeintertwiner )
4388    local f;
4389    f := gq!.basefield;
4390    if not IsEvenInt(Characteristic(f)) then
4391        Error( "<gq> must be a GQ over a field of even characteristic");
4392    fi;
4393    if IsSymplecticSpace(gq) then
4394        return SelfDualitySymplectic(gq, computeintertwiner );
4395    elif IsParabolicQuadric( gq ) then
4396        return SelfDualityParabolic(gq, computeintertwiner );
4397    else
4398        Error("<gq> must be a symplectic GQ or a parabolic quadric");
4399    fi;
4400end );
4401
4402# Added july 2014 jdb.
4403#############################################################################
4404#O  SelfDuality( <gq>, <bool> )
4405# This is the interface to the helper functions. It returns SelfDuality(<gq>,true)
4406##
4407InstallMethod( SelfDuality,
4408	"for a GQ and a boolean",
4409	[ IsClassicalGQ ],
4410	function( gq )
4411        return SelfDuality(gq, true);
4412end );
4413
4414
4415