diff --git a/doc/sandwich.xml b/doc/sandwich.xml
new file mode 100644
index 000000000..5e4d89a89
--- /dev/null
+++ b/doc/sandwich.xml
@@ -0,0 +1,130 @@
+#############################################################################
+##
+#W sandwich.xml
+#Y Copyright (C) 2024 Murray T. Whyte
+##
+## Licensing information can be found in the README file of this package.
+##
+#############################################################################
+##
+
+<#GAPDoc Label="SandwichSemigroup">
+
+
+ The sandwich variant semigroup of the given semigroup, with respect
+ to the given element.
+
+ The sandwich semigroup of a semigroup S with respect to an element
+ a of S is the semigroup with the
+ same underlying set as S, but with multiplication *
+ defined as x * y = x a y.
+
+ This operation returns a semigroup isomorphic to the sandwich semigroup
+ of S with respect to a.
+
+ T := FullTransformationMonoid(5);
+
+gap> S := SandwichSemigroup(T, Transformation([1, 1]));
+
+<#/GAPDoc>
+
+<#GAPDoc Label="BijectionSandwichSemigroup">
+
+
+
+ An isomorphism from semigroup S to the sandwich semigroup of
+ S with respect to a.
+
+
+ The sandwich semigroup of S with respect to a
+ mathematically has the same underlying set as S,
+ but is represented with a different set of elements in
+ &SEMIGROUPS;. This function returns a mapping which is an isomorphism
+ from S to the sandwich semigroup of S with respect
+ a.
+
+ T := FullTransformationMonoid(5);
+
+f := BijectionSandwichSemigroup(T, Transformation([1, 1]));
+MappingByFunction( , , function( s ) ... end, function( s ) ... end)
+gap> Transformation([3, 2, 2]) ^ f;
+]]>
+<#/GAPDoc>
+
+<#GAPDoc Label="IsSandwichSemigroupElement">
+
+
+ Returns true if elt has the representation of a
+ sandwich semigroup element.
+
+ Elements of a sandwich semigroup obtained using
+ normally lie in this
+ category. The exception is elements obtained by applying
+ the map to elements already
+ in this category. That is, the elements of a semigroup lie in the
+ category if and only if the
+ elements of the corresponding dual semigroup do not.
+
+ S := SingularPartitionMonoid(4);;
+gap> D := DualSemigroup(S);;
+gap> s := GeneratorsOfSemigroup(S)[1];;
+gap> map := AntiIsomorphismDualSemigroup(S);;
+gap> t := s ^ map;
+<
+ in the dual semigroup>
+gap> IsDualSemigroupElement(t);
+true
+gap> inv := InverseGeneralMapping(map);;
+gap> x := t ^ inv;
+
+gap> IsDualSemigroupElement(x);
+false]]>
+<#/GAPDoc>
+
+<#GAPDoc Label="IsDualSemigroupRep">
+
+
+ Returns true if sgrp lies in the category of
+ dual semigroups.
+
+ Semigroups created using
+ normally lie in this category. The exception is semigroups
+ which are the dual of semigroups already lying in this category.
+ That is, a semigroup lies in the category
+ if and only if the corresponding
+ dual semigroup does not. Note that this is not a Representation in the
+ GAP sense, and will likely be renamed in a future major release of the
+ package.
+
+ S := Semigroup([Transformation([3, 5, 1, 1, 2]),
+> Transformation([1, 2, 4, 4, 3])]);
+
+gap> D := DualSemigroup(S);
+>
+gap> IsDualSemigroupRep(D);
+true
+gap> R := DualSemigroup(D);
+
+gap> IsDualSemigroupRep(R);
+false
+gap> R = S;
+true
+gap> T := Range(IsomorphismTransformationSemigroup(D));
+
+gap> IsDualSemigroupRep(T);
+false
+gap> x := Representative(D);
+
+gap> V := Semigroup(x);
+>
+gap> IsDualSemigroupRep(V);
+true]]>
+<#/GAPDoc>
diff --git a/gap/attributes/sandwich.gd b/gap/attributes/sandwich.gd
new file mode 100644
index 000000000..ac949b91a
--- /dev/null
+++ b/gap/attributes/sandwich.gd
@@ -0,0 +1,25 @@
+############################################################################
+##
+## attributes/sandwich.gd
+## Copyright (C) 2024 Murray T. Whyte
+##
+## Licensing information can be found in the README file of this package.
+##
+#############################################################################
+##
+
+DeclareCategory("IsSandwichSemigroupElement", IsAssociativeElement);
+DeclareCategoryCollections("IsSandwichSemigroupElement");
+DeclareOperation("SandwichSemigroup", [IsSemigroup, IsAssociativeElement]);
+DeclareCategory("IsSandwichSemigroup", IsSemigroup);
+DeclareAttribute("SandwichElement", IsSandwichSemigroup);
+DeclareAttribute("SandwichSemigroupOfFamily", IsFamily);
+DeclareOperation("BijectionSandwichSemigroup",
+ [IsSemigroup, IsAssociativeElement]);
+
+DeclareSynonym("IsSandwichSubsemigroup",
+ IsSemigroup and IsSandwichSemigroupElementCollection);
+
+InstallTrueMethod(CanUseGapFroidurePin, IsSandwichSubsemigroup);
+DeclareAttribute("InverseBijectionSandwichSemigroup",
+ IsSandwichSemigroup);
diff --git a/gap/attributes/sandwich.gi b/gap/attributes/sandwich.gi
new file mode 100644
index 000000000..827d2c69e
--- /dev/null
+++ b/gap/attributes/sandwich.gi
@@ -0,0 +1,167 @@
+#############################################################################
+##
+## attributes/sandwich.gi
+## Copyright (C) 2024 Murray T. Whyte
+##
+## Licensing information can be found in the README file of this package.
+##
+#############################################################################
+##
+
+# This file contains an implementation of sandwich variants of semigroups.
+#
+# TODO: Write a method for getting generating sets for sandwich semigroups.
+
+InstallMethod(SandwichSemigroup, "for a semigroup and an element",
+[IsSemigroup, IsAssociativeElement],
+function(S, a)
+ local fam, sandwich, filts, type, forward, backward, map;
+
+ if not a in S then
+ ErrorNoReturn("expected 2nd argument to be an element of 1st argument");
+ fi;
+
+ fam := NewFamily("SandwichSemigroupElementsFamily",
+ IsSandwichSemigroupElement, CanEasilyCompareElements);
+ sandwich := rec();
+ Objectify(NewType(CollectionsFamily(fam),
+ IsSandwichSemigroup and
+ IsWholeFamily and
+ IsAttributeStoringRep),
+ sandwich);
+ filts := IsSandwichSemigroupElement;
+
+ type := NewType(fam, filts);
+ fam!.type := type;
+
+ SetSandwichSemigroupOfFamily(fam, sandwich);
+ SetElementsFamily(FamilyObj(sandwich), fam);
+
+ # TODO(MTW) set the bijection from the original semigroup into the sandwich
+ SetSandwichElement(sandwich, a);
+ SetSandwichElement(fam, a);
+
+ SetUnderlyingSemigroup(sandwich, S);
+ SetUnderlyingSemigroup(fam, S);
+
+ forward := s -> SEMIGROUPS.SandwichSemigroupElementNC(sandwich, s);
+ backward := s -> s![1];
+
+ map := MappingByFunction(sandwich, S, backward, forward);
+ SetInverseBijectionSandwichSemigroup(sandwich, map);
+
+ return sandwich;
+end);
+
+SEMIGROUPS.SandwichSemigroupElementNC := function(SandwichSemigroup, s)
+ return Objectify(ElementsFamily(FamilyObj(SandwichSemigroup))!.type, [s]);
+end;
+
+InstallMethod(BijectionSandwichSemigroup, "for a semigroup and an element",
+[IsSemigroup, IsAssociativeElement],
+function(S, a)
+ local forward, backward, sandwich_S;
+
+ sandwich_S := SandwichSemigroup(S, a);
+
+ forward := s -> SEMIGROUPS.SandwichSemigroupElementNC(sandwich_S, s);
+ backward := s -> s![1];
+
+ return MappingByFunction(S, sandwich_S, forward, backward);
+end);
+
+## Technical methods
+InstallMethod(\*, "for sandwich semigroup elements",
+IsIdenticalObj,
+[IsSandwichSemigroupElement, IsSandwichSemigroupElement],
+{x, y} -> Objectify(FamilyObj(x)!.type, [x![1] * SandwichElement(FamilyObj(x)) * y![1]]));
+
+InstallMethod(\=, "for sandwich semigroup elements",
+IsIdenticalObj,
+[IsSandwichSemigroupElement, IsSandwichSemigroupElement],
+{x, y} -> x![1] = y![1]);
+
+InstallMethod(Size, "for a sandwich semigroup",
+[IsSandwichSemigroup],
+S -> Size(UnderlyingSemigroup(S)));
+
+InstallMethod(AsList, "for a sandwich semigroup",
+[IsSandwichSemigroup],
+10, # add rank to beat enumeration methods
+S -> List(UnderlyingSemigroup(S), s -> SEMIGROUPS.SandwichSemigroupElementNC(S, s)));
+
+InstallMethod(\<, "for sandwich semigroup elements",
+IsIdenticalObj,
+[IsSandwichSemigroupElement, IsSandwichSemigroupElement],
+{x, y} -> x![1] < y![1]);
+
+InstallMethod(AsSSortedList, "for a sandwich semigroup",
+[IsSandwichSemigroup], S -> SortedList(AsList(S)));
+
+InstallMethod(ChooseHashFunction, "for a sandwich semigroup element and int",
+[IsSandwichSemigroupElement, IsInt],
+function(x, data)
+ local H, hashfunc;
+
+ H := ChooseHashFunction(x![1], data);
+ hashfunc := {a, b} -> H.func(a![1], b);
+ return rec(func := hashfunc, data := H.data);
+end);
+
+InstallMethod(PrintObj, "for a sandwich semigroup",
+[IsSandwichSemigroup],
+function(S)
+# If we know the name of the underlying semigroup, it would be cool to use it
+ Print("");
+end);
+
+InstallMethod(ViewObj, "for a sandwich semigroup",
+[IsSandwichSemigroup], PrintObj);
+
+InstallMethod(PrintObj, "for a sandwich semigroup element",
+[IsSandwichSemigroupElement],
+function(x)
+ Print("<", x![1], " in sandwich semigroup>");
+ end);
+
+InstallMethod(ViewObj, "for a sandwich semigroup element",
+[IsSandwichSemigroupElement], PrintObj);
+
+InstallMethod(GeneratorsOfSemigroup, "for a sandwich semigroup",
+[IsSandwichSemigroup],
+function(S)
+ local T, a, i, P, A, B, map, gens, U, D, y, j, layer, x;
+
+ T := UnderlyingSemigroup(S);
+ a := SandwichElement(S);
+ i := Position(DClasses(T), DClass(T, a));
+ P := PartialOrderOfDClasses(T);
+ A := VerticesReachableFrom(P, i);
+ AddSet(A, i);
+ B := Difference(DigraphVertices(P), A);
+
+ map := InverseGeneralMapping(InverseBijectionSandwichSemigroup(S));
+
+ gens := [];
+ for j in B do
+ Append(gens, List(DClasses(T)[j], x -> x ^ map));
+ od;
+
+ U := Semigroup(gens);
+
+ while Size(U) < Size(S) do
+ for layer in DigraphLayers(P, i) do
+ for j in layer do
+ D := DClasses(T)[j];
+ for x in D do
+ y := x ^ map;
+ if not y in U then
+ Add(gens, y);
+ U := Semigroup(gens);
+ fi;
+ od;
+ od;
+ od;
+ od;
+ return gens;
+end);
diff --git a/init.g b/init.g
index 1c01899ef..f6d205422 100644
--- a/init.g
+++ b/init.g
@@ -122,6 +122,7 @@ ReadPackage("semigroups", "gap/attributes/isorms.gd");
ReadPackage("semigroups", "gap/attributes/maximal.gd");
ReadPackage("semigroups", "gap/attributes/properties.gd");
ReadPackage("semigroups", "gap/attributes/homomorph.gd");
+ReadPackage("semigroups", "gap/attributes/sandwich.gd");
ReadPackage("semigroups", "gap/attributes/semifp.gd");
ReadPackage("semigroups", "gap/attributes/translat.gd");
ReadPackage("semigroups", "gap/attributes/rms-translat.gd");
diff --git a/read.g b/read.g
index f89150a42..bc3b1b7d6 100644
--- a/read.g
+++ b/read.g
@@ -80,6 +80,7 @@ ReadPackage("semigroups", "gap/attributes/isomorph.gi");
ReadPackage("semigroups", "gap/attributes/isorms.gi");
ReadPackage("semigroups", "gap/attributes/maximal.gi");
ReadPackage("semigroups", "gap/attributes/properties.gi");
+ReadPackage("semigroups", "gap/attributes/sandwich.gi");
ReadPackage("semigroups", "gap/attributes/semifp.gi");
ReadPackage("semigroups", "gap/attributes/translat.gi");
ReadPackage("semigroups", "gap/attributes/rms-translat.gi");