--- /dev/null
+from sage.all import *
+
+def face_generated_by(K,S):
+ r"""
+ Return the intersection of all faces of ``K`` that contain ``S``.
+
+ REFERENCES:
+
+ .. [Tam] Bit-Shun Tam. On the duality operator of a convex cone. Linear
+ Algebra and its Applications, 64:33-56, 1985, doi:10.1016/0024-3795(85)
+ 90265-4.
+
+ SETUP::
+
+ sage: from mjo.cone.faces import face_generated_by
+
+ EXAMPLES:
+
+ The face generated by a standard basis vector in the nonnegative
+ orthant should be the cone consisting of all nonnegative multiples
+ of that standard basis vector::
+
+ sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)])
+ sage: F = Cone([(1,0,0)])
+ sage: face_generated_by(K,F) == F
+ True
+
+ If we take a nontrivial combination of standard basis vectors in the
+ nonnegative orthant, then the face that the combination generates
+ should be the cone generated by those two basis vectors::
+
+ sage: e1 = vector(QQ, [1,0,0])
+ sage: e2 = vector(QQ, [0,1,0])
+ sage: e3 = vector(QQ, [0,0,1])
+ sage: K = Cone([e1,e2,e2])
+ sage: F = [e1/2 + e2/2]
+ sage: face_generated_by(K,F).is_equivalent(Cone([e1,e2]))
+ True
+
+ An error is raised if ``S`` is not contained in ``K``::
+
+ sage: K = Cone([(1,0),(0,1)])
+ sage: S = [(1,1), (-1,3)]
+ sage: face_generated_by(K,S)
+ Traceback (most recent call last):
+ ...
+ ValueError: S is not a subset of the cone
+
+ TESTS:
+
+ The face generated by a set should be a face::
+
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8, max_rays=10)
+ sage: S = [K.random_element() for idx in range(0,5)]
+ sage: F = face_generated_by(K, S)
+ sage: F.is_face_of(K)
+ True
+
+ The face generated a set should always contain that set::
+
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8, max_rays=10)
+ sage: S = [K.random_element() for idx in range(0,5)]
+ sage: F = face_generated_by(K, S)
+ sage: all([F.contains(x) for x in S])
+ True
+
+ The generators of a proper cone are all extreme vectors of the cone,
+ and therefore generate their own faces::
+
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: max_rays=10,
+ ....: strictly_convex=True,
+ ....: solid=True)
+ sage: all([face_generated_by(K, [r]) == Cone([r]) for r in K])
+ True
+
+ """
+ face_lattice = K.face_lattice()
+
+ # A list comprehension doesn't work and don't ask me why.
+ candidates = [F for F in face_lattice if all([F.contains(x) for x in S])]
+
+ # K itself is a face of K, so unless we were given a set S that
+ # isn't a subset of K, the candidates list will be nonempty.
+ if len(candidates) == 0:
+ raise ValueError('S is not a subset of the cone')
+ else:
+ return face_lattice.sorted(candidates)[0]