Wildmeshing Toolkit
Loading...
Searching...
No Matches
TriMesh.cpp
Go to the documentation of this file.
1#include "TriMesh.hpp"
2
4#include <numeric>
9
10namespace wmtk {
11
12TriMesh::~TriMesh() = default;
14 : MeshCRTP<TriMesh>(2)
15 , m_vf_handle(register_attribute_typed<int64_t>("m_vf", PrimitiveType::Vertex, 1, false, -1))
16 , m_ef_handle(register_attribute_typed<int64_t>("m_ef", PrimitiveType::Edge, 1, false, -1))
17 , m_fv_handle(register_attribute_typed<int64_t>("m_fv", PrimitiveType::Triangle, 3, false, -1))
18 , m_fe_handle(register_attribute_typed<int64_t>("m_fe", PrimitiveType::Triangle, 3, false, -1))
19 , m_ff_handle(register_attribute_typed<int64_t>("m_ff", PrimitiveType::Triangle, 3, false, -1))
20{
22}
23
25{
26 m_vf_accessor = std::make_unique<attribute::Accessor<int64_t, TriMesh>>(*this, m_vf_handle);
27 m_ef_accessor = std::make_unique<attribute::Accessor<int64_t, TriMesh>>(*this, m_ef_handle);
28 m_fv_accessor = std::make_unique<attribute::Accessor<int64_t, TriMesh>>(*this, m_fv_handle);
29 m_fe_accessor = std::make_unique<attribute::Accessor<int64_t, TriMesh>>(*this, m_fe_handle);
30 m_ff_accessor = std::make_unique<attribute::Accessor<int64_t, TriMesh>>(*this, m_ff_handle);
31}
32
34 : MeshCRTP<TriMesh>(std::move(o))
35{
36 m_vf_handle = o.m_vf_handle;
37 m_ef_handle = o.m_ef_handle;
38 m_fv_handle = o.m_fv_handle;
39 m_fe_handle = o.m_fe_handle;
40 m_ff_handle = o.m_ff_handle;
41
43}
45{
46 Mesh::operator=(std::move(o));
47 m_vf_handle = o.m_vf_handle;
48 m_ef_handle = o.m_ef_handle;
49 m_fv_handle = o.m_fv_handle;
50 m_fe_handle = o.m_fe_handle;
51 m_ff_handle = o.m_ff_handle;
52
54 return *this;
55}
56
57
58bool TriMesh::is_boundary(PrimitiveType pt, const Tuple& tuple) const
59{
60 switch (pt) {
61 case PrimitiveType::Vertex: return is_boundary_vertex(tuple);
62 case PrimitiveType::Edge: return is_boundary_edge(tuple);
65 default: break;
66 }
67 assert(
68 false); // "tried to compute the boundary of an tri mesh for an invalid simplex dimension"
69 return false;
70}
71
72bool TriMesh::is_boundary_edge(const Tuple& tuple) const
73{
74 assert(is_valid(tuple));
75 return m_ff_accessor->const_vector_attribute<3>(tuple)(tuple.local_eid()) < 0;
76}
77
78bool TriMesh::is_boundary_vertex(const Tuple& vertex) const
79{
80 // go through all edges and check if they are boundary
81 // const simplex::SimplexCollection neigh = simplex::open_star(*this, Simplex::vertex(vertex));
82 // for (const Simplex& s : neigh.get_edges()) {
83 // if (is_boundary(s.tuple())) {
84 // return true;
85 // }
86 //}
87
88 Tuple t = vertex;
89 do {
90 if (is_boundary_edge(t)) {
91 return true;
92 }
94 } while (t != vertex);
95
96 return false;
97}
98
100{
101 assert(is_valid(tuple));
102 bool ccw = is_ccw(tuple);
103
104 switch (type) {
106 const int64_t gvid = id(tuple, PrimitiveType::Vertex);
107 const int64_t geid = id(tuple, PrimitiveType::Edge);
108 const int64_t gfid = id(tuple, PrimitiveType::Triangle);
109
110 auto ff = m_ff_accessor->const_vector_attribute<3>(tuple);
111
112 int64_t gcid_new = ff(tuple.local_eid());
113 int64_t lvid_new = -1, leid_new = -1;
114
115 auto fv = m_fv_accessor->index_access().const_vector_attribute<3>(gcid_new);
116
117 auto fe = m_fe_accessor->index_access().const_vector_attribute<3>(gcid_new);
118
119 if (gfid == gcid_new) {
120 // this supports 0,1,0 triangles not 0,0,0 triangles
121 int64_t oleid = tuple.local_eid();
122 int64_t olvid = tuple.local_vid();
123 for (int64_t i = 0; i < 3; ++i) {
124 if (i != oleid && fe(i) == geid) {
125 leid_new = i;
126 }
127 }
128 // if the old vertex is no "opposite of the old or new edges
129 // then they share the vertex
130 // a
131 // 0/ \1 <-- 0 and c share local ids, 1 and b share local ids
132 // /____\.
133 // b c
134 if (oleid != olvid && leid_new != olvid) {
135 lvid_new = olvid;
136 } else {
137 for (int64_t i = 0; i < 3; ++i) {
138 if (i != olvid && fv(i) == gvid) {
139 lvid_new = i;
140 }
141 }
142 }
143 } else {
144 for (int64_t i = 0; i < 3; ++i) {
145 if (fe(i) == geid) {
146 leid_new = i;
147 }
148 if (fv(i) == gvid) {
149 lvid_new = i;
150 }
151 }
152 }
153 assert(lvid_new != -1);
154 assert(leid_new != -1);
155
156 const Tuple res(lvid_new, leid_new, tuple.local_fid(), gcid_new);
157 assert(is_valid(res));
158 return res;
159 }
163 default: {
164 assert(false);
165 return autogen::tri_mesh::local_switch_tuple(tuple, type);
166 }
167 }
168}
169
170bool TriMesh::is_ccw(const Tuple& tuple) const
171{
172 assert(is_valid(tuple));
173 return autogen::tri_mesh::is_ccw(tuple);
174}
175
177 Eigen::Ref<const RowVectors3l> FV,
178 Eigen::Ref<const RowVectors3l> FE,
179 Eigen::Ref<const RowVectors3l> FF,
180 Eigen::Ref<const VectorXl> VF,
181 Eigen::Ref<const VectorXl> EF)
182{
183 // reserve memory for attributes
184
185
186 std::vector<int64_t> cap{
187 static_cast<int64_t>(VF.rows()),
188 static_cast<int64_t>(EF.rows()),
189 static_cast<int64_t>(FF.rows())};
190
191 set_capacities(cap);
192
193
194 // get Accessors for topology
195 attribute::Accessor<int64_t> fv_accessor = create_accessor<int64_t>(m_fv_handle);
196 attribute::Accessor<int64_t> fe_accessor = create_accessor<int64_t>(m_fe_handle);
197 attribute::Accessor<int64_t> ff_accessor = create_accessor<int64_t>(m_ff_handle);
198 attribute::Accessor<int64_t> vf_accessor = create_accessor<int64_t>(m_vf_handle);
199 attribute::Accessor<int64_t> ef_accessor = create_accessor<int64_t>(m_ef_handle);
200
204
205 // iterate over the matrices and fill attributes
206 for (int64_t i = 0; i < capacity(PrimitiveType::Triangle); ++i) {
207 fv_accessor.index_access().vector_attribute<3>(i) = FV.row(i).transpose();
208 fe_accessor.index_access().vector_attribute<3>(i) = FE.row(i).transpose();
209 ff_accessor.index_access().vector_attribute<3>(i) = FF.row(i).transpose();
210
211 f_flag_accessor.index_access().activate(i);
212 }
213 // m_vf
214 for (int64_t i = 0; i < capacity(PrimitiveType::Vertex); ++i) {
215 auto& vf = vf_accessor.index_access().scalar_attribute(i);
216 vf = VF(i);
217 v_flag_accessor.index_access().activate(i);
218 }
219 // m_ef
220 for (int64_t i = 0; i < capacity(PrimitiveType::Edge); ++i) {
221 auto& ef = ef_accessor.index_access().scalar_attribute(i);
222 ef = EF(i);
223 e_flag_accessor.index_access().activate(i);
224 }
225}
226
227void TriMesh::initialize(Eigen::Ref<const RowVectors3l> F, bool is_free)
228{
229 this->m_is_free = is_free;
230 auto [FE, FF, VF, EF] = trimesh_topology_initialization(F);
231 if (is_free) {
232 FF.setConstant(-1);
233 }
234 initialize(F, FE, FF, VF, EF);
235}
236void TriMesh::initialize_free(int64_t count)
237{
238 // 0 1 2
239 // 3 4 5
240 RowVectors3l S(count, 3);
241 std::iota(S.data(), S.data() + S.size(), int64_t(0));
242 initialize(S, true);
243}
244
245Tuple TriMesh::tuple_from_global_ids(int64_t fid, int64_t eid, int64_t vid) const
246{
247 auto fv = m_fv_accessor->index_access().const_vector_attribute<3>(fid);
248 auto fe = m_fe_accessor->index_access().const_vector_attribute<3>(fid);
249
250
251 int64_t lvid = -1;
252 int64_t leid = -1;
253
254 for (int j = 0; j < 3; ++j) {
255 if (fv(j) == vid) {
256 lvid = j;
257 }
258 if (fe(j) == eid) {
259 leid = j;
260 }
261 }
262 assert(lvid != -1);
263 assert(leid != -1);
264
265 return Tuple(lvid, leid, -1, fid);
266}
267
268Tuple TriMesh::tuple_from_id(const PrimitiveType type, const int64_t gid) const
269{
270 switch (type) {
272 return vertex_tuple_from_id(gid);
273 }
274 case PrimitiveType::Edge: {
275 return edge_tuple_from_id(gid);
276 }
278 return face_tuple_from_id(gid);
279 }
281 throw std::runtime_error("no tet tuple supported for trimesh");
282 break;
283 }
284 default: assert(false); // "Invalid primitive type"
285 }
286 return Tuple();
287}
288
290{
291 auto f = m_vf_accessor->index_access().const_scalar_attribute(id);
292 auto fv = m_fv_accessor->index_access().const_vector_attribute<3>(f);
293 for (int64_t i = 0; i < 3; ++i) {
294 if (fv(i) == id) {
296 }
297 }
298 assert(false); // "vertex_tuple_from_id failed"
299
300 return Tuple();
301}
302
304{
305 auto f = m_ef_accessor->index_access().const_scalar_attribute(id);
306 auto fe = m_fe_accessor->index_access().const_vector_attribute<3>(f);
307 for (int64_t i = 0; i < 3; ++i) {
308 if (fe(i) == id) {
310 }
311 }
312 assert(false); // "edge_tuple_from_id failed"
313
314 return Tuple();
315}
316
318{
319 Tuple f_tuple = Tuple(
322 -1,
323 id);
324 assert(is_ccw(f_tuple));
325 assert(is_valid(f_tuple));
326 return f_tuple;
327}
328
329bool TriMesh::is_valid(const Tuple& tuple) const
330{
331 if (!Mesh::is_valid(tuple)) {
332 logger().trace("Tuple was null and therefore not valid");
333 return false;
334 }
335 const bool is_connectivity_valid = tuple.local_vid() >= 0 && tuple.local_eid() >= 0 &&
336 tuple.global_cid() >= 0 &&
338
340#if !defined(NDEBUG)
341 logger().trace(
342 "tuple.local_vid()={} >= 0 && tuple.local_eid()={} >= 0 &&"
343 " tuple.global_cid()={} >= 0 &&"
344 " autogen::tri_mesh::tuple_is_valid_for_ccw(tuple)={}",
345 tuple.local_vid(),
346 tuple.local_eid(),
347 tuple.global_cid(),
349 assert(tuple.local_vid() >= 0);
350 assert(tuple.local_eid() >= 0);
351 assert(tuple.global_cid() >= 0);
353#endif
354 return false;
355 }
356 return true;
357}
358
360{
361 // get Accessors for topology
362 const attribute::Accessor<int64_t> fv_accessor = create_const_accessor<int64_t>(m_fv_handle);
363 const attribute::Accessor<int64_t> fe_accessor = create_const_accessor<int64_t>(m_fe_handle);
364 const attribute::Accessor<int64_t> ff_accessor = create_const_accessor<int64_t>(m_ff_handle);
365 const attribute::Accessor<int64_t> vf_accessor = create_const_accessor<int64_t>(m_vf_handle);
366 const attribute::Accessor<int64_t> ef_accessor = create_const_accessor<int64_t>(m_ef_handle);
367 const attribute::FlagAccessor<TriMesh> v_flag_accessor =
370 const attribute::FlagAccessor<TriMesh> f_flag_accessor =
372
373
374 for (int64_t i = 0; i < capacity(PrimitiveType::Triangle); ++i) {
375 if (!f_flag_accessor.index_access().is_active(i)) {
376 continue;
377 }
378 auto fe = fe_accessor.index_access().const_vector_attribute<3>(i);
379 auto fv = fv_accessor.index_access().const_vector_attribute<3>(i);
380
381 bool bad_face = false;
382
383 for (int64_t j = 0; j < 3; ++j) {
384 int64_t ei = fe(j);
385 int64_t vi = fv(j);
386 if (!e_flag_accessor.index_access().is_active(ei)) {
387 wmtk::logger().error(
388 "Face {} refers to edge {} at local index {} which was deleted",
389 i,
390 ei,
391 j);
392 bad_face = true;
393 }
394 if (!v_flag_accessor.index_access().is_active(vi)) {
395 wmtk::logger().error(
396 "Face {} refers to vertex{} at local index {} which was deleted",
397 i,
398 vi,
399 j);
400 bad_face = true;
401 }
402 }
403 if (bad_face) {
404 return false;
405 }
406 }
407 // EF and FE
408 for (int64_t i = 0; i < capacity(PrimitiveType::Edge); ++i) {
409 if (!e_flag_accessor.index_access().is_active(i)) {
410 continue;
411 }
412 int cnt = 0;
413 long ef_val = ef_accessor.index_access().const_scalar_attribute(i);
414
415 auto fe_val = fe_accessor.index_access().const_vector_attribute<3>(ef_val);
416 for (int64_t j = 0; j < 3; ++j) {
417 if (fe_val(j) == i) {
418 cnt++;
419 }
420 }
421 if (cnt == 0) {
422 wmtk::logger().error(
423 "EF[{0}] {1} and FE:[EF[{0}]] = {2} are not "
424 "compatible ",
425 i,
426 ef_val,
427 fmt::join(fe_val, ","));
428
429 // std::cout << "EF and FE not compatible" << std::endl;
430 return false;
431 }
432 }
433
434 // VF and FV
435 for (int64_t i = 0; i < capacity(PrimitiveType::Vertex); ++i) {
436 const int64_t vf = vf_accessor.index_access().const_scalar_attribute(i);
437 if (!v_flag_accessor.index_access().is_active(i)) {
438 continue;
439 }
440 int cnt = 0;
441
442 auto fv = fv_accessor.index_access().const_vector_attribute<3>(vf);
443 for (int64_t j = 0; j < 3; ++j) {
444 if (fv(j) == i) {
445 cnt++;
446 }
447 }
448 if (cnt == 0) {
449 wmtk::logger().error(
450 "VF and FV not compatible, could not find VF[{}] = {} "
451 "in FV[{}] = [{}]",
452 i,
453 vf,
454 vf,
455 fmt::join(fv, ","));
456 return false;
457 }
458 }
459
460 // FE and EF
461 for (int64_t i = 0; i < capacity(PrimitiveType::Triangle); ++i) {
462 if (!f_flag_accessor.index_access().is_active(i)) {
463 continue;
464 }
465 auto fe = fe_accessor.index_access().const_vector_attribute<3>(i);
466 auto ff = ff_accessor.index_access().const_vector_attribute<3>(i);
467
468 for (int64_t j = 0; j < 3; ++j) {
469 int neighbor_fid = ff(j);
470 const bool is_boundary = neighbor_fid == -1;
471 if (is_boundary) {
472 auto ef = ef_accessor.index_access().const_scalar_attribute(fe(j));
473 if (ef != i) {
474 wmtk::logger().error(
475 "Even though local edge {} of face {} is "
476 "boundary (global eid is {}), "
477 "ef[{}] = {} != {}",
478 j,
479 i,
480 fe(j),
481 fe(j),
482 ef,
483 i);
484 return false;
485 }
486 } else {
487 if (neighbor_fid == i) {
488 logger().error(
489 "Connectivity check cannot work when mapping a "
490 "face to itself (face {})",
491 i);
492 assert(false);
493 continue;
494 }
495 auto neighbor_ff =
496 ff_accessor.index_access().const_vector_attribute<3>(neighbor_fid);
497
498 if ((neighbor_ff.array() == i).any()) {
499 auto neighbor_fe =
500 fe_accessor.index_access().const_vector_attribute<3>(neighbor_fid);
501
502 int edge_shared_count = 0;
503 for (int local_neighbor_eid = 0; local_neighbor_eid < 3; ++local_neighbor_eid) {
504 // find some edge which is shared
505 if (neighbor_ff(local_neighbor_eid) == i) {
506 if (fe(j) == neighbor_fe(local_neighbor_eid)) {
507 edge_shared_count++;
508 }
509 }
510 }
511 if (edge_shared_count != 1) {
512 wmtk::logger().error(
513 "face {} with fe={} neighbor fe[{}] = {} "
514 "was unable to find itself "
515 "uniquely (found {})",
516 i,
517 fmt::join(fe, ","),
518 neighbor_fid,
519 fmt::join(neighbor_fe, ","),
520 edge_shared_count);
521 return false;
522 }
523 } else {
524 wmtk::logger().error(
525 "face {} with ff={} neighbor ff[{}] = {} was "
526 "unable to find itself",
527 i,
528 fmt::join(ff, ","),
529 neighbor_fid,
530 fmt::join(neighbor_ff, ","));
531 return false;
532 }
533 }
534 }
535 }
536
537 return true;
538}
539
541{
542 Tuple r(t.local_vid(), t.local_eid(), t.local_fid(), cid);
543 return r;
544}
545
546std::vector<std::vector<TypedAttributeHandle<int64_t>>> TriMesh::connectivity_attributes() const
547{
548 std::vector<std::vector<TypedAttributeHandle<int64_t>>> handles(3);
549
550 handles[2].push_back(m_vf_handle);
551 handles[2].push_back(m_ef_handle);
552 handles[2].push_back(m_ff_handle);
553
554 handles[1].push_back(m_fe_handle);
555
556 handles[0].push_back(m_fv_handle);
557
558 return handles;
559}
560
561std::vector<Tuple> TriMesh::orient_vertices(const Tuple& tuple) const
562{
563 int64_t cid = tuple.global_cid();
564 return {Tuple(0, 2, -1, cid), Tuple(1, 0, -1, cid), Tuple(2, 1, -1, cid)};
565}
566
567
568} // namespace wmtk
A Curiously Recurring Template Pattern shim to enable generic specialization of functions.
Definition MeshCRTP.hpp:24
bool m_is_free
Definition Mesh.hpp:832
void set_capacities(std::vector< int64_t > capacities)
int64_t capacity(PrimitiveType type) const
read in the m_capacities return the upper bound for the number of entities of the given dimension
bool is_free() const
Definition Mesh.hpp:973
Mesh & operator=(const Mesh &other)=delete
virtual bool is_valid(const Tuple &tuple) const
check validity of tuple including its hash
Definition Mesh.cpp:113
const attribute::FlagAccessor< Mesh > get_flag_accessor(PrimitiveType type) const
Definition Mesh.cpp:159
std::vector< std::vector< TypedAttributeHandle< int64_t > > > connectivity_attributes() const override
Returns a vector of vectors of attribute handles.
Definition TriMesh.cpp:546
Tuple switch_face(const Tuple &tuple) const
Definition TriMesh.hpp:121
Tuple face_tuple_from_id(int64_t id) const
Definition TriMesh.cpp:317
Tuple switch_tuple(const Tuple &tuple, PrimitiveType type) const final override
switch the orientation of the Tuple of the given dimension
Definition TriMesh.cpp:99
bool is_boundary_vertex(const Tuple &tuple) const
Definition TriMesh.cpp:78
attribute::TypedAttributeHandle< int64_t > m_vf_handle
Definition TriMesh.hpp:90
bool is_boundary(PrimitiveType pt, const Tuple &tuple) const final override
returns if a simplex is on the boundary of hte mesh. For anything but dimension - 1 this checks if th...
Definition TriMesh.cpp:58
Tuple tuple_from_id(const PrimitiveType type, const int64_t gid) const final override
internal function that returns the tuple of requested type, and has the global index cid
Definition TriMesh.cpp:268
std::unique_ptr< attribute::Accessor< int64_t, TriMesh > > m_ef_accessor
Definition TriMesh.hpp:98
bool is_valid(const Tuple &tuple) const final override
check validity of tuple including its hash
Definition TriMesh.cpp:329
Tuple vertex_tuple_from_id(int64_t id) const
Definition TriMesh.cpp:289
bool is_connectivity_valid() const final override
Definition TriMesh.cpp:359
Tuple tuple_from_global_ids(int64_t fid, int64_t eid, int64_t vid) const
Definition TriMesh.cpp:245
void make_cached_accessors()
Definition TriMesh.cpp:24
std::unique_ptr< attribute::Accessor< int64_t, TriMesh > > m_fe_accessor
Definition TriMesh.hpp:100
std::unique_ptr< attribute::Accessor< int64_t, TriMesh > > m_fv_accessor
Definition TriMesh.hpp:99
void initialize_free(int64_t count)
Definition TriMesh.cpp:236
Tuple switch_edge(const Tuple &tuple) const
Definition TriMesh.hpp:117
static Tuple with_different_cid(const Tuple &t, int64_t cid)
Definition TriMesh.cpp:540
void initialize(Eigen::Ref< const RowVectors3l > FV, Eigen::Ref< const RowVectors3l > FE, Eigen::Ref< const RowVectors3l > FF, Eigen::Ref< const VectorXl > VF, Eigen::Ref< const VectorXl > EF)
Definition TriMesh.cpp:176
attribute::TypedAttributeHandle< int64_t > m_ff_handle
Definition TriMesh.hpp:95
attribute::TypedAttributeHandle< int64_t > m_ef_handle
Definition TriMesh.hpp:91
TriMesh & operator=(const TriMesh &o)=delete
int64_t id(const Tuple &tuple, PrimitiveType type) const
Definition TriMesh.hpp:125
std::unique_ptr< attribute::Accessor< int64_t, TriMesh > > m_ff_accessor
Definition TriMesh.hpp:101
bool is_boundary_edge(const Tuple &tuple) const
Definition TriMesh.cpp:72
bool is_ccw(const Tuple &tuple) const final override
returns if a tuple is counterclockwise or not
Definition TriMesh.cpp:170
attribute::TypedAttributeHandle< int64_t > m_fv_handle
Definition TriMesh.hpp:93
Tuple edge_tuple_from_id(int64_t id) const
Definition TriMesh.cpp:303
std::vector< Tuple > orient_vertices(const Tuple &t) const override
Definition TriMesh.cpp:561
std::unique_ptr< attribute::Accessor< int64_t, TriMesh > > m_vf_accessor
Definition TriMesh.hpp:97
attribute::TypedAttributeHandle< int64_t > m_fe_handle
Definition TriMesh.hpp:94
~TriMesh() override
The Tuple is the basic navigation tool in our mesh data structure.
Definition Tuple.hpp:19
int8_t local_vid() const
Definition Tuple.hxx:52
int8_t local_fid() const
Definition Tuple.hxx:62
int8_t local_eid() const
Definition Tuple.hxx:57
int64_t global_cid() const
Definition Tuple.hxx:47
A CachingAccessor that uses tuples for accessing attributes instead of indices.
Definition Accessor.hpp:28
CachingBaseType & index_access()
Definition Accessor.hpp:97
T const_scalar_attribute(const int64_t index) const
T & scalar_attribute(const int64_t index)
ConstMapResult< D > const_vector_attribute(const int64_t index) const
MapResult< D > vector_attribute(const int64_t index)
const IndexBaseType & index_access() const
const int64_t auto_2d_table_complete_vertex[3][2]
Tuple get_tuple_from_simplex_local_vertex_id(int8_t local_id, int64_t global_id)
Tuple get_tuple_from_simplex_local_edge_id(int8_t local_id, int64_t global_id)
bool tuple_is_valid_for_ccw(const Tuple &t)
Definition is_ccw.hxx:15
Tuple local_switch_tuple(const Tuple &t, PrimitiveType pt)
bool is_ccw(const Tuple &t)
Definition is_ccw.hxx:9
RowVectors< int64_t, 3 > RowVectors3l
Definition Types.hpp:47
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:58
std::tuple< RowVectors3l, RowVectors3l, VectorXl, VectorXl > trimesh_topology_initialization(Eigen::Ref< const RowVectors3l > F)