54 Class Functions

This chapter introduces class functions and group characters in GAP3.

First section Why Group Characters tells about the ideas why to use these structures besides the characters and character tables described in chapters Character Tables and Characters.

The subsequent section More about Class Functions tells details about the implementation of group characters and class functions in GAP3.

Sections Operators for Class Functions and Functions for Class Functions tell about the operators and functions for class functions and (virtual) characters.

Sections ClassFunction, VirtualCharacter, Character, and Irr describe how to construct such class functions and group characters.

Sections IsClassFunction, IsVirtualCharacter, and IsCharacter describe the characteristic functions of class functions and virtual characters.

Then sections InertiaSubgroup and OrbitsCharacters describe other functions for characters.

Then sections Storing Subgroup Information, NormalSubgroupClasses, ClassesNormalSubgroup, and FactorGroupNormalSubgroupClasses tell about some functions and record components to access and store frequently used (normal) subgroups.

The final section Class Function Records describes the records that implement class functions.

In this chapter, all examples use irreducible characters of the symmetric group S4. For running the examples, you must first define the group and its characters as follows.

    gap> S4:= SolvableGroup( "S4" );;
    gap> irr:= Irr( S4 );; 

Subsections

  1. Why Group Characters
  2. More about Class Functions
  3. Operators for Class Functions
  4. Functions for Class Functions
  5. ClassFunction
  6. VirtualCharacter
  7. Character
  8. IsClassFunction
  9. IsVirtualCharacter
  10. IsCharacter
  11. Irr
  12. InertiaSubgroup
  13. OrbitsCharacters
  14. Storing Subgroup Information
  15. NormalSubgroupClasses
  16. ClassesNormalSubgroup
  17. FactorGroupNormalSubgroupClasses
  18. Class Function Records

54.1 Why Group Characters

When one says ``χ is a character of a group G'' then this object χ carries a lot of information. χ has certain properties such as being irreducible or not. Several subgroups of G are related to χ, such as the kernel and the centre of χ. And one can apply operators to χ, such as forming the conjugate character under the action of an automorphism of G, or computing the determinant of χ.

In GAP3, the characters known from chapters Character Tables and Characters are just lists of character values. This has several disadvantages. Firstly one cannot store knowledge about a character directly in the character, and secondly for every computation that requires more than just the character values one has to regard this list explicitly as a character belonging to a character table. In practice this means that the user has the task to put the objects into the right context, or --more concrete-- the user has to supply lots of arguments.

This works nicely for characters that are used without groups, like characters of library tables. And if one deals with incomplete character tables often it is necessary to specify the arguments explicitly, for example one has to choose a fusion map or power map from a set of possibilities.

But for dealing with a group and its characters, and maybe also subgroups and their characters, it is desirable that GAP3 keeps track of the interpretation of characters.

Because of this it seems to be useful to introduce an alternative concept where a group character in GAP3 is represented as a record that contains the character values, the underlying group or character table, an appropriate operations record, and all the knowledge about the group character.

Together with characters, also the more general class functions and virtual characters are implemented.

Here is an example that shows both approaches. First we define the groups.

    gap> S4:= SolvableGroup( "S4" );;
    gap> D8:= SylowSubgroup( S4, 2 );; D8.name:= "D8";; 

We do some computations using the functions described in chapters Characters and Character Tables.

    gap> t   := CharTable( S4 );;
    gap> tD8 := CharTable( D8 );;
    gap> FusionConjugacyClasses( D8, S4 );;
    gap> chi:= tD8.irreducibles[2];
    [ 1, -1, 1, 1, -1 ]
    gap> Tensored( [ chi ], [ chi ] )[1];
    [ 1, 1, 1, 1, 1 ]
    gap> ind:= Induced( tD8, t, [ chi ] )[1];
    [ 3, -1, 0, 1, -1 ]
    gap> List( t.irreducibles, x -> ScalarProduct( t, x, ind ) );
    [ 0, 0, 0, 1, 0 ]
    gap> det:= DeterminantChar( t, ind );
    [ 1, 1, 1, -1, -1 ]
    gap> cent:= CentralChar( t, ind );
    [ 1, -1, 0, 2, -2 ]
    gap> rest:= Restricted( t, tD8, [ cent ] )[1];
    [ 1, -1, -1, 2, -2 ] 

And now we do the same calculations with the class function records.

    gap> irr   := Irr( S4 );;
    gap> irrD8 := Irr( D8 );;
    gap> chi:= irrD8[2];
    Character( D8, [ 1, -1, 1, 1, -1 ] )
    gap> chi * chi;
    Character( D8, [ 1, 1, 1, 1, 1 ] )
    gap> ind:= chi ^ S4;
    Character( S4, [ 3, -1, 0, 1, -1 ] )
    gap> List( irr, x -> ScalarProduct( x, ind ) );
    [ 0, 0, 0, 1, 0 ]
    gap> det:= Determinant( ind );
    Character( S4, [ 1, 1, 1, -1, -1 ] )
    gap> cent:= Omega( ind );
    ClassFunction( S4, [ 1, -1, 0, 2, -2 ] )
    gap> rest:= Character( D8, cent );
    Character( D8, [ 1, -1, -1, 2, -2 ] ) 

Of course we could have used the Induce and Restricted function also for lists of class functions.

    gap> Induced( tD8, t, tD8.irreducibles{ [ 1, 3 ] } );
    [ [ 3, 3, 0, 1, 1 ], [ 3, 3, 0, -1, -1 ] ]
    gap> Induced( irrD8{ [ 1, 3 ] }, S4 );
    [ Character( S4, [ 3, 3, 0, 1, 1 ] ), 
      Character( S4, [ 3, 3, 0, -1, -1 ] ) ] 

If one deals with complete character tables then often the table provides enough information, so it is possible to use the table instead of the group.

    gap> s5 := CharTable( "A5.2" );; irrs5 := Irr( s5  );;
    gap> m11:= CharTable( "M11"  );; irrm11:= Irr( m11 );;
    gap> irrs5[2];
    Character( CharTable( "A5.2" ), [ 1, 1, 1, 1, -1, -1, -1 ] )
    gap> irrs5[2] ^ m11;
    Character( CharTable( "M11" ), [ 66, 2, 3, -2, 1, -1, 0, 0, 0, 0 ] )
    gap> Determinant( irrs5[4] );
    Character( CharTable( "A5.2" ), [ 1, 1, 1, 1, -1, -1, -1 ] ) 

In this case functions that compute normal subgroups related to characters will return the list of class positions corresponding to that normal subgroup.

    gap> Kernel( irrs5[2] );
    [ 1, 2, 3, 4 ] 

But if we ask for non-normal subgroups of course there is no chance to get an answer without the group, for example inertia subgroups cannot be computed from character tables.

54.2 More about Class Functions

Let G be a finite group. A class function of G is a function from G into the complex numbers (or a subfield of the complex numbers) that is constant on conjugacy classes of G. Addition, multiplication, and scalar multiplication of class functions are defined pointwise. Thus the set of all class functions of G is an algebra (or ring, or vector space).

Class functions and (virtual) group characters

Every mapping with source G that is constant on conjugacy classes of G is called a class function of G. Differences of characters of G are called virtual characters of G.

Class functions occur in a natural way when one deals with characters. For example, the central character of a group character is only a class function.

Every character is a virtual character, and every virtual character is a class function. Any function or operator that is applicable to a class function can of course be applied to a (virtual) group character. There are functions only for (virtual) group characters, like IsIrreducible, which doesn't make sense for a general class function, and there are also functions that do not make sense for virtual characters but only for characters, like Determinant.

Class functions as mappings

In GAP3, class functions of a group G are mappings (see chapter Mappings) with source G and range Cyclotomics (or a subfield). All operators and functions for mappings (like Image Image, PreImages PreImages) can be applied to class functions.

Note, however, that the operators * and ^ allow also other arguments than mappings do (see Operators for Class Functions).

54.3 Operators for Class Functions

chi = psi
chi < psi

Equality and comparison of class functions are defined as for mappings (see Comparisons of Mappings); in case of equal source and range the values components are used to compute the result.

    gap> irr[1]; irr[2];
    Character( S4, [ 1, 1, 1, 1, 1 ] )
    Character( S4, [ 1, 1, 1, -1, -1 ] )
    gap> irr[1] < irr[2];
    false
    gap> irr[1] > irr[2];
    true
    gap> irr[1] = Irr( SolvableGroup( "S4" ) )[1];
    false    # The groups are different. 

chi + psi
chi - psi

+ and - denote the addition and subtraction of class functions.

n * chi
chi * psi

* denotes (besides the composition of mappings, see Operations for Mappings) the multiplication of a class function chi with a scalar n and the tensor product of two class functions.

chi / n

/ denotes the division of the class function chi by a scalar n.

    gap> psi:= irr[3] * irr[4];
    Character( S4, [ 6, -2, 0, 0, 0 ] )
    gap> psi:= irr[3] - irr[1];
    VirtualCharacter( S4, [ 1, 1, -2, -1, -1 ] )
    gap> phi:= psi * irr[4];
    VirtualCharacter( S4, [ 3, -1, 0, -1, 1 ] )
    gap> IsCharacter( phi ); phi;
    true
    Character( S4, [ 3, -1, 0, -1, 1 ] )
    gap> psi:= ( 3 * irr[2] - irr[3] ) * irr[4];
    VirtualCharacter( S4, [ 3, -1, 0, -3, 3 ] )
    gap> 2 * psi ;
    VirtualCharacter( S4, [ 6, -2, 0, -6, 6 ] )
    gap> last / 3;
    ClassFunction( S4, [ 2, -2/3, 0, -2, 2 ] ) 

chi ^ n
g ^ chi

denote the tensor power by a nonnegative integer n and the image of the group element g, like for all mappings (see Operations for Mappings).

chi ^ g

is the conjugate class function by the group element g, that must be an element of the parent of the source of chi or something else that acts on the source via ^. If chi.source is not a permutation group then g may also be a permutation that is interpreted as acting by permuting the classes (This maybe useful for table characters.).

chi ^ G

is the induced class function.

    gap> V4:= Subgroup( S4, S4.generators{ [ 3, 4 ] } );
    Subgroup( S4, [ c, d ] )
    gap> V4.name:= "V4";;
    gap> V4irr:= Irr( V4 );;
    gap> chi:= V4irr[3];
    Character( V4, [ 1, -1, 1, -1 ] )
    gap> chi ^ S4;
    Character( S4, [ 6, -2, 0, 0, 0 ] )
    gap> chi ^ S4.2;
    Character( V4, [ 1, -1, -1, 1 ] )
    gap> chi ^ ( S4.2 ^ 2 );
    Character( V4, [ 1, 1, -1, -1 ] )
    gap> S4.3 ^ chi; S4.4 ^ chi;
    1
    -1
    gap> chi ^ 2;
    Character( V4, [ 1, 1, 1, 1 ] ) 

54.4 Functions for Class Functions

Besides the usual mapping functions (see chapter Mappings for the details.), the following polymorphic functions are overlaid in the operations records of class functions and (virtual) characters. They are listed in alphabetical order.

Centre( chi ):

centre of a class function

Constituents( chi ):

set of irreducible characters of a virtual character

Degree( chi ):

degree of a class function

Determinant( chi ):

determinant of a character

Display( chi ):

displays the class function with the table head

Induced( list, G ):

induced class functions corresp. to class functions in the list list from subgroup H to group G

IsFaithful( chi ):

property check (virtual characters only)

IsIrreducible( chi ):

property check (characters only)

Kernel( chi ):

kernel of a class function

Norm( chi ):

norm of class function

Omega( chi ):

central character

Print( chi ):

prints a class function

Restricted( list, H ):

restrictions of class functions in the list list to subgroup H

ScalarProduct( chi, psi ):

scalar product of two class functions

54.5 ClassFunction

ClassFunction( G, values )

returns the class function of the group G with values list values.

ClassFunction( G, chi )

returns the class function of G corresponding to the class function chi of H. The group H can be a factor group of G, or G can be a subgroup or factor group of H.

    gap> phi:= ClassFunction( S4, [ 1, -1, 0, 2, -2 ] );
    ClassFunction( S4, [ 1, -1, 0, 2, -2 ] )
    gap> coeff:= List( irr, x -> ScalarProduct( x, phi ) );
    [ -1/12, -1/12, -1/6, 5/4, -3/4 ]
    gap> ClassFunction( S4, coeff );
    ClassFunction( S4, [ -1/12, -1/12, -1/6, 5/4, -3/4 ] )
    gap> syl2:= SylowSubgroup( S4, 2 );;
    gap> ClassFunction( syl2, phi );
    ClassFunction( D8, [ 1, -1, -1, 2, -2 ] ) 

54.6 VirtualCharacter

VirtualCharacter( G, values )

returns the virtual character of the group G with values list values.

VirtualCharacter( G, chi )

returns the virtual character of G corresponding to the virtual character chi of H. The group H can be a factor group of G, or G can be a subgroup or factor group of H.

    gap> syl2:= SylowSubgroup( S4, 2 );;
    gap> psi:= VirtualCharacter( S4, [ 0, 0, 3, 0, 0 ] );
    VirtualCharacter( S4, [ 0, 0, 3, 0, 0 ] )
    gap> VirtualCharacter( syl2, psi );
    VirtualCharacter( D8, [ 0, 0, 0, 0, 0 ] )
    gap> S3:= S4 / V4;
    Group( a, b )
    gap> VirtualCharacter( S3, irr[3] );
    VirtualCharacter( Group( a, b ), [ 2, -1, 0 ] ) 

Note that it is not checked whether the result is really a virtual character.

54.7 Character

Character( repres )

returns the character of the group representation repres.

Character( G, values )

returns the character of the group G with values list values.

Character( G, chi )

returns the character of G corresponding to the character chi with source H. The group H can be a factor group of G, or G can be a subgroup or factor group of H.

    gap> syl2:= SylowSubgroup( S4, 2 );;
    gap> Character( syl2, irr[3] );
    Character( D8, [ 2, 2, 2, 0, 0 ] )
    gap> S3:= S4 / V4;
    Group( a, b )
    gap> Character( S3, irr[3] );
    Character( Group( a, b ), [ 2, -1, 0 ] )
    gap> reg:= Character( S4, [ 24, 0, 0, 0, 0 ] );
    Character( S4, [ 24, 0, 0, 0, 0 ] ) 

Note that it is not checked whether the result is really a character.

54.8 IsClassFunction

IsClassFunction( obj )

returns true if obj is a class function, and false otherwise.

    gap> chi:= S4.charTable.irreducibles[3];
    [ 2, 2, -1, 0, 0 ]
    gap> IsClassFunction( chi );
    false
    gap> irr[3];
    Character( S4, [ 2, 2, -1, 0, 0 ] )
    gap> IsClassFunction( irr[3] );
    true 

54.9 IsVirtualCharacter

IsVirtualCharacter( obj )

returns true if obj is a virtual character, and false otherwise. For a class function obj that does not know whether it is a virtual character, the scalar products with all irreducible characters of the source of obj are computed. If they are all integral then obj is turned into a virtual character record.

    gap> psi:= irr[3] - irr[1];
    VirtualCharacter( S4, [ 1, 1, -2, -1, -1 ] )
    gap> cf:= ClassFunction( S4, [ 1, 1, -2, -1, -1 ] );
    ClassFunction( S4, [ 1, 1, -2, -1, -1 ] )
    gap> IsVirtualCharacter( cf );
    true
    gap> IsCharacter( cf );
    false
    gap> cf;
    VirtualCharacter( S4, [ 1, 1, -2, -1, -1 ] ) 

54.10 IsCharacter

IsCharacter( obj )

returns true if obj is a character, and false otherwise. For a class function obj that does not know whether it is a character, the scalar products with all irreducible characters of the source of obj are computed. If they are all integral and nonegative then obj is turned into a character record.

    gap> psi:= ClassFunction( S4, S4.charTable.centralizers );
    ClassFunction( S4, [ 24, 8, 3, 4, 4 ] )
    gap> IsCharacter( psi ); psi;
    true
    Character( S4, [ 24, 8, 3, 4, 4 ] )
    gap> cf:= ClassFunction( S4, irr[3] - irr[1] );
    ClassFunction( S4, [ 1, 1, -2, -1, -1 ] )
    gap> IsCharacter( cf ); cf;
    false
    VirtualCharacter( S4, [ 1, 1, -2, -1, -1 ] ) 

54.11 Irr

Irr( G )

returns the list of irreducible characters of the group G. If necessary the character table of G is computed. The succession of characters is the same as in CharTable( G ).

    gap> Irr( SolvableGroup( "S4" ) );
    [ Character( S4, [ 1, 1, 1, 1, 1 ] ), 
      Character( S4, [ 1, 1, 1, -1, -1 ] ), 
      Character( S4, [ 2, 2, -1, 0, 0 ] ), 
      Character( S4, [ 3, -1, 0, 1, -1 ] ), 
      Character( S4, [ 3, -1, 0, -1, 1 ] ) ] 

54.12 InertiaSubgroup

InertiaSubgroup( G, chi )

For a class function chi of a normal subgroup N of the group G, InertiaSubgroup( G, chi ) returns the inertia subgroup IG(chi), that is, the subgroup of all those elements g ∈ G that satisfy <chi> \g = chi.

    gap> V4:= Subgroup( S4, S4.generators{ [ 3, 4 ] } );
    Subgroup( S4, [ c, d ] )
    gap> irrsub:= Irr( V4 );
    #W  Warning: Group has no name
    [ Character( Subgroup( S4, [ c, d ] ), [ 1, 1, 1, 1 ] ), 
      Character( Subgroup( S4, [ c, d ] ), [ 1, 1, -1, -1 ] ), 
      Character( Subgroup( S4, [ c, d ] ), [ 1, -1, 1, -1 ] ), 
      Character( Subgroup( S4, [ c, d ] ), [ 1, -1, -1, 1 ] ) ]
    gap> List( irrsub, x -> InertiaSubgroup( S4, x ) );
    [ Subgroup( S4, [ a, b, c, d ] ), Subgroup( S4, [ a*b^2, c, d ] ),
      Subgroup( S4, [ a*b, c, d ] ), Subgroup( S4, [ a, c, d ] ) ] 

54.13 OrbitsCharacters

OrbitsCharacters( irr )

returns a list of orbits of the characters irr under the action of Galois automorphisms and multiplication with linear characters in irr. This is used for functions that need to consider only representatives under the operation of this group, like TestSubnormallyMonomial.

OrbitsCharacters works also for irr a list of character value lists. In this case the result contains orbits of these lists.

Note that OrbitsCharacters does not require that irr is closed under the described action, so the function may also be used to complete the orbits.

    gap> irr:= Irr( SolvableGroup( "S4" ) );;
    gap> OrbitsCharacters( irr );
    [ [ Character( S4, [ 1, 1, 1, -1, -1 ] ),
          Character( S4, [ 1, 1, 1, 1, 1 ] ) ],
      [ Character( S4, [ 2, 2, -1, 0, 0 ] ) ],
      [ Character( S4, [ 3, -1, 0, -1, 1 ] ),
          Character( S4, [ 3, -1, 0, 1, -1 ] ) ] ]
    gap> OrbitsCharacters( List( irr{ [1,2,4] }, x -> x.values ) );
    [ [ [ 1, 1, 1, -1, -1 ], [ 1, 1, 1, 1, 1 ] ],
      [ [ 3, -1, 0, 1, -1 ], [ 3, -1, 0, -1, 1 ] ] ] 

54.14 Storing Subgroup Information

Many computations for a group character χ of a group G, such as that of kernel or centre of χ, involve computations in (normal) subgroups or factor groups of G.

There are two aspects that make it reasonable to store relevant information used in these computations.

First it is possible to use the character table of a group for computations with the group. For example, suppose we know for every normal subgroup N the list of positions of conjugacy classes that form N. Then we can compute the intersection of normal subgroups efficiently by intersecting the corresponding lists.

Second one should try to reuse (expensive) information one has computed. Suppose you need the character table of a certain subgroup U that was constructed for example as inertia subgroup of a character. Then it may be probable that this group has been constructed already. So one should look whether U occurs in a list of interesting subgroups for that the tables are already known.

This section lists several data structures that support storing and using information about subgroups.

Storing Normal Subgroup Information

In some cases a question about a normal subgroup N can be answered efficiently if one knows the character table of G and the G-conjugacy classes that form N, e.g., the question whether a character of G restricts irreducibly to N. But other questions require the computation of the group N or even more information, e.g., if we want to know whether a character restricts homogeneously to N this will in general require the computation of the character table of N.

In order to do such computations only once, we introduce three components in the group record of G to store normal subgroups, the corresponding lists of conjugacy classes, and (if known) the factor groups, namely

nsg:

a list of (not necessarily all) normal subgroups of G,

nsgclasses:

at position i the list of positions of conjugacy classes forming the i-th entry of the nsg component,

nsgfactors:

at position i (if bound) the factor group modulo the i-th entry of the nsg component.

The functions

NormalSubgroupClasses,
FactorGroupNormalSubgroupClasses,
ClassesNormalSubgroup

initialize these components and update them. They are the only functions that do this.

So if you need information about a normal subgroup of G for that you know the G-conjugacy classes, you should get it using NormalSubgroupClasses. If the normal subgroup was already stored it is just returned, with all the knowledge it contains. Otherwise the normal subgroup is computed and added to the lists, and will be available for the next call.

Storing information for computing conjugate class functions

The computation of conjugate class functions requires the computation of permutatins of the list of conjugacy classes. In order to minimize the number of membership tests in conjugacy classes it is useful to store a partition of classes that is respected by every admissible permutation. This is stored in the component globalPartitionClasses.

If the normalizer N of H in its parent is stored in H, or if H is normal in its parent then the component permClassesHomomorphism is used. It holds the group homomorphism mapping every element of N to the induced permutation of classes.

Both components are generated automatically when they are needed.

Storing inertia subgroup information

Let N be the normalizer of H in its parent, and χ a character of H. The inertia subgroup IN(χ) is the stabilizer in N of χ under conjugation of class functions. Characters with same value distribution, like Galois conjugate characters, have the same inertia subgroup. It seems to be useful to store this information. For that, the inertiaInfo component of H is initialized when needed, a record with components partitions and stabilizers, both lists. The stabilizers component contains the stabilizer in N of the corresponding partition.

54.15 NormalSubgroupClasses

NormalSubgroupClasses( G, classes )

returns the normal subgroup of the group G that consists of the conjugacy classes whose positions are in the list classes.

If G.nsg does not contain the required normal subgroup, and if G contains the component G.normalSubgroups then the result and the group in G.normalSubgroups will be identical.

    gap> ccl:= ConjugacyClasses( S4 );
    [ ConjugacyClass( S4, IdAgWord ), ConjugacyClass( S4, d ), 
      ConjugacyClass( S4, b ), ConjugacyClass( S4, a ), 
      ConjugacyClass( S4, a*d ) ]
    gap> NormalSubgroupClasses( S4, [ 1, 2 ] );
    Subgroup( S4, [ c, d ] ) 

The list of classes corresponding to a normal subgroup is returned by ClassesNormalSubgroup.

54.16 ClassesNormalSubgroup

ClassesNormalSubgroup( G, N )

returns the list of positions of conjugacy classes of the group G that are contained in the normal subgroup N of G.

    gap> ccl:= ConjugacyClasses( S4 );
    [ ConjugacyClass( S4, IdAgWord ), ConjugacyClass( S4, d ), 
      ConjugacyClass( S4, b ), ConjugacyClass( S4, a ), 
      ConjugacyClass( S4, a*d ) ]
    gap> V4:= NormalClosure( S4, Subgroup( S4, [ S4.4 ] ) );
    Subgroup( S4, [ c, d ] )
    gap> ClassesNormalSubgroup( S4, V4 );
    [ 1, 2 ] 

The normal subgroup corresponding to a list of classes is returned by NormalSubgroupClasses.

54.17 FactorGroupNormalSubgroupClasses

FactorGroupNormalSubgroupClasses( G, classes )

returns the factor group of the group G modulo the normal subgroup of G that consists of the conjugacy classes whose positions are in the list classes.

    gap> ccl:= ConjugacyClasses( S4 );
    [ ConjugacyClass( S4, IdAgWord ), ConjugacyClass( S4, d ), 
      ConjugacyClass( S4, b ), ConjugacyClass( S4, a ), 
      ConjugacyClass( S4, a*d ) ]
    gap> S3:= FactorGroupNormalSubgroupClasses( S4, [ 1, 2 ] );
    Group( a, b ) 

54.18 Class Function Records

Every class function has the components

isClassFunction :

always true,

source:

the underlying group (or character table),

values:

the list of values, corresponding to the conjugacyClasses component of source,

operations:

the operations record which is one of ClassFunctionOps, VirtualCharacterOps, CharacterOps.

Optional components are

isVirtualCharacter:

The class function knows to be a virtual character.

isCharacter:

The class function knows to be a character.

Previous Up Next
Index

gap3-jm
27 Nov 2023