9 We use a Dynkin diagrams based on data in Knapp, appendix C. Most of
13 o --> o --> o -- ... --> o .
15 For B, C, F, the nodes may be of different weights. For these, the
16 Coxeter element is { 1, 2, ..., n } or { n, n - 1, ..., 1 } (see
19 For E_{6,7,8}, the node ordering is odd, so the Coxeter element needs
20 to be considered carefully.
22 For A_{2n + 1}, the ordering is extremely odd, due to the cyclic
23 nature of the extended Dynkin diagram:
34 Note that the weights in the quiver are backwards from the weights in
35 the Dynkin diagrams: for example, the normal diagram for C_n is
37 (1) -- (1) -- (1) -- ... -- (1) -- (2)
39 however, when we construct the quiver, the weights will be
41 (2) -- (2) -- (2) -- ... -- (2) -- (1).
45 impl std.equatable KC_type
46 impl std.hashable KC_type
48 const parse_KC_type : (str : byte[:] -> std.result(KC_type, byte[:]))
49 const get_n : (kc : KC_type -> int)
50 const get_weight : (kc : KC_type, j : weyl_reflection -> (int,int))
51 const coxeter_num : (kc : KC_type -> int)
52 const get_edges : (kc : KC_type -> (weyl_reflection, weyl_reflection)[:])
53 const get_tree_partition : (kc : KC_type -> weyl_reflection[:][:])
54 const get_simple_roots : (kc : KC_type -> vector#[:])
55 const get_fundamental_weights : (kc : KC_type -> vector#[:])
56 const inv_coxeter : (kc : KC_type, s : weyl_reflection -> std.size)
59 Functions below this comment have their results cached: do
60 not free results (even error strings, if those are possible)
62 TODO: move more functions down to here..
64 const get_coxeter_elt : (kc : KC_type -> std.result(weyl_reflection[:], byte[:]))
65 const get_cartan_matrix : (kc : KC_type -> int[:][:])
66 const get_w0 : (kc : KC_type -> weyl_reflection[:])
69 var cartan_matrix_stored : std.htab(KC_type, int[:][:])#
70 var coxeter_elt_stored : std.htab(KC_type, std.result(weyl_reflection[:], byte[:]))#
71 var w0_stored : std.htab(KC_type, weyl_reflection[:])#
74 cartan_matrix_stored = std.mkht()
75 coxeter_elt_stored = std.mkht()
76 w0_stored = std.mkht()
80 for (key, val) : std.byhtkeyvals(cartan_matrix_stored)
83 std.htfree(cartan_matrix_stored)
85 for (key, val) : std.byhtkeyvals(coxeter_elt_stored)
87 | `std.Ok x: __dispose__(x)
88 | `std.Err e: std.slfree(e)
91 std.htfree(coxeter_elt_stored)
93 for (key, val) : std.byhtkeyvals(w0_stored)
99 impl std.equatable KC_type =
102 | (`A n1, `A n2): -> n1 == n2
103 | (`B n1, `B n2): -> n1 == n2
104 | (`C n1, `C n2): -> n1 == n2
105 | (`D n1, `D n2): -> n1 == n2
106 | (`E6, `E6): -> true
107 | (`E7, `E7): -> true
108 | (`E8, `E8): -> true
109 | (`F4, `F4): -> true
110 | (`G2, `G2): -> true
116 impl std.hashable KC_type =
119 | `A n: -> (n : uint64) ^ (0x100 << 32)
120 | `B n: -> (n : uint64) ^ (0x101 << 32)
121 | `C n: -> (n : uint64) ^ (0x110 << 32)
122 | `D n: -> (n : uint64) ^ (0x111 << 32)
132 const parse_KC_type = { str : byte[:]
134 -> `std.Err std.fmt("Unknown Killing—Cartan type “{}”", str)
138 match std.intparse(str[1:])
139 | `std.Some nn: n = nn
140 | `std.None: -> `std.Err std.fmt("Unknown Killing—Cartan type “{}”", str)
144 -> `std.Err std.fmt("Killing—Cartan subscript “{}” should be positive", str[1:])
147 match (std.toupper((str[0] : char)), n)
148 | ('A', _): -> `std.Ok `A n
149 | ('B', _): -> `std.Ok `B n
150 | ('C', _): -> `std.Ok `C n
151 | ('D', _): -> `std.Ok `D n
152 | ('E', 6): -> `std.Ok `E6
153 | ('E', 7): -> `std.Ok `E7
154 | ('E', 8): -> `std.Ok `E8
155 | ('E', _): -> `std.Err std.fmt("Killing—Cartan type E must be one of “E6”, “E7”, “E8”")
156 | ('F', 4): -> `std.Ok `F4
157 | ('F', _): -> `std.Err std.fmt("Killing—Cartan type F must be “F4”")
158 | ('G', 2): -> `std.Ok `G2
159 | ('G', _): -> `std.Err std.fmt("Killing—Cartan type G must be “G2”")
160 | (c, _): -> `std.Err std.fmt("Killing—Cartan type {} unknown/unsupported", c)
164 const coxeter_num = {kc : KC_type
182 const get_n = {kc : KC_type
197 The coxeter element is 1-based. For C2 is it [1, 2][:]. This is
198 because the entries should be used for vertex names, not arithmetic.
200 const get_coxeter_elt = {kc : KC_type
201 match std.htget(coxeter_elt_stored, kc)
202 | `std.Some stored: -> stored
208 | `E6: ret = `std.Ok std.sldup([2,4,3,5,1,6][:])
209 | `E7: ret = `std.Ok std.sldup([7,6,5,4,2,3,1][:])
210 | `E8: ret = `std.Ok std.sldup([8,7,6,5,4,2,3,1][:])
211 | `G2: ret = `std.Ok std.sldup([1, 2][:])
213 var l : weyl_reflection[:] = [][:]
214 for var j = 1; j <= nn; ++j
215 std.slpush(&l, (j : weyl_reflection))
221 The result will not be a Coxeter element
222 corresponding to a well-rooted, tree-like quiver. In
223 the case n is even, this gives garbage. In the case n
224 is odd, this gives an anti-well-rooted tree-like
228 ret = `std.Err std.fmt("Coxeter elements for A_2n do not form w0")
230 var l : weyl_reflection[:] = [][:]
232 for var j = (nn + 1) / 2; j >= 1; --j
233 std.slpush(&l, (j : weyl_reflection))
235 std.slpush(&l, (nn - j + 1 : weyl_reflection))
242 /* Take the easy way out */
243 var l : weyl_reflection[:] = [][:]
245 for var j = 0; j < n; ++j
246 std.slpush(&l, (j + 1 : weyl_reflection))
252 std.htput(coxeter_elt_stored, kc, ret)
257 Based on the Dynkin diagram, as encoded by get_edges and get_weight.
259 const get_cartan_matrix = {kc
260 match std.htget(cartan_matrix_stored, kc)
261 | `std.Some stored: -> stored
266 var edges : (weyl_reflection, weyl_reflection)[:] = auto get_edges(kc)
268 var A : int[:][:] = std.slalloc((rank : std.size))
269 for var j = 0; j < rank; ++j
270 A[j] = std.slalloc((rank : std.size))
271 for var k = 0; k < rank; ++k
281 var wj = get_weight(kc, j).1
282 var wk = get_weight(kc, k).1
284 /* Quick, dumb gcd that works for this specific case only */
285 var d = std.min(wj, wk)
290 A[j - 1][k - 1] = -1 * wk / d
291 A[k - 1][j - 1] = -1 * wj / d
294 std.htput(cartan_matrix_stored, kc, A)
298 const get_edges = {kc
299 var edges : (weyl_reflection, weyl_reflection)[:] = [][:]
302 for var i = 1; i + 1 <= (n + 1) / 2; ++i
303 std.slpush(&edges, ((i + 1 : weyl_reflection), (i : weyl_reflection)))
304 std.slpush(&edges, ((n - i : weyl_reflection), (n - i + 1 : weyl_reflection)))
312 for var i = 1; i + 1 <= (n - 2); ++i
313 std.slpush(&edges, ((i : weyl_reflection), (i + 1 : weyl_reflection)))
315 std.slpush(&edges, ((n - 2 : weyl_reflection), (n - 1 : weyl_reflection)))
316 std.slpush(&edges, ((n - 2 : weyl_reflection), (n - 0 : weyl_reflection)))
320 | `E6: -> std.sldup([(2, 4), (4, 5), (5, 6), (4, 3), (3, 1)][:])
321 | `E7: -> std.sldup([(7,6), (6,5), (5,4), (4,2), (4,3), (3,1)][:])
322 | `E8: -> std.sldup([(8,7), (7,6), (6,5), (5,4), (4,2), (4,3), (3,1)][:])
325 for var i = 1; i + 1 <= nn; ++i
326 std.slpush(&edges, ((i : weyl_reflection), (i + 1 : weyl_reflection)))
334 All from Knapp. Return { ω_1, ω_2, …, ω_n}, with each given as
335 coefficients of the standard vector basis { e_i }.
337 So, for E8, ω_1 is NOT [ 4, 5, 7, ... ][:]. It is 4α_1 + 5α_2 + ...
339 const get_fundamental_weights = {kc
340 var ret : vector#[:] = [][:]
342 var zero : yakmo.Q# = yakmo.gid()
343 var one : yakmo.Q# = yakmo.rid()
346 var mone : yakmo.Q# = auto yakmo.gneg(one)
347 var half : yakmo.Q# = auto std.try(yakmo.Qfrom(1,2))
348 var mhalf : yakmo.Q# = auto std.try(yakmo.Qfrom(-1,2))
350 var coeffs : (int[:], int)[:] = [][:]
353 for var j = 0; j < n; ++j
354 var new_w = std.slalloc((n + 1 : std.size))
355 for var k = 0; k <= j; ++k
356 new_w[k] = t.dup(one)
358 for var k = j + 1; k < n + 1; ++k
359 new_w[k] = t.dup(zero)
362 var summand = auto std.try(yakmo.Qfrom(-1 * (j + 1), n + 1))
363 for var k = 0; k < n + 1; ++k
364 yakmo.gadd_ip(new_w[k], summand)
367 std.slpush(&ret, std.mk([ .c = new_w ]))
372 for var j = 0; j < n - 1; ++j
373 var new_w = std.slalloc((n : std.size))
374 for var k = 0; k < j + 1; ++k
375 new_w[k] = t.dup(one)
377 for var k = j + 1; k < n; ++k
378 new_w[k] = t.dup(zero)
380 std.slpush(&ret, std.mk([ .c = new_w ]))
383 var w_n = std.slalloc((n : std.size))
384 for var k = 0; k < n; ++k
387 std.slpush(&ret, std.mk([ .c = w_n ]))
391 for var j = 0; j < n; ++j
392 var new_w = std.slalloc((n : std.size))
393 for var k = 0; k < j + 1; ++k
394 new_w[k] = t.dup(one)
396 for var k = j + 1; k < n; ++k
397 new_w[k] = t.dup(zero)
399 std.slpush(&ret, std.mk([ .c = new_w ]))
405 -> get_fundamental_weights(`A 1)
408 for var j = 0; j < n - 2; ++j
409 var new_w = std.slalloc((n : std.size))
410 for var k = 0; k < j + 1; ++k
411 new_w[k] = t.dup(one)
413 for var k = j + 1; k < n; ++k
414 new_w[k] = t.dup(zero)
416 std.slpush(&ret, std.mk([ .c = new_w ]))
419 var w_nm1 = std.slalloc((n : std.size))
420 for var k = 0; k < n - 1; ++k
421 w_nm1[k] = t.dup(half)
423 w_nm1[n - 1] = t.dup(mhalf)
424 std.slpush(&ret, std.mk([ .c = w_nm1 ]))
426 var w_n = std.slalloc((n : std.size))
427 for var k = 0; k < n - 1; ++k
430 w_n[n - 1] = t.dup(half)
431 std.slpush(&ret, std.mk([ .c = w_n ]))
435 /* For the exceptional ones, construct in terms of simple roots */
438 ([ 4, 3, 5, 6, 4, 2][:], 3),
439 ([ 1, 2, 2, 3, 2, 1][:], 1),
440 ([ 5, 6, 10, 12, 8, 4][:], 3),
441 ([ 2, 3, 4, 6, 4, 2][:], 1),
442 ([ 4, 6, 8, 12, 10, 5][:], 3),
443 ([ 2, 3, 4, 6, 5, 4][:], 3),
447 ([ 2, 2, 3, 4, 3, 2, 1][:], 1),
448 ([ 4, 7, 8, 12, 9, 6, 3][:], 2),
449 ([ 3, 4, 6, 8, 6, 4, 2][:], 1),
450 ([ 4, 6, 8, 12, 9, 6, 3][:], 1),
451 ([ 6, 9, 12, 18, 15, 10, 5][:], 2),
452 ([ 2, 3, 4, 6, 5, 4, 2][:], 1),
453 ([ 2, 3, 4, 6, 5, 4, 3][:], 2),
457 ([ 4, 5, 7, 10, 8, 6, 4, 2][:], 1),
458 ([ 5, 8, 10, 15, 12, 9, 6, 3][:], 1),
459 ([ 7, 10, 14, 20, 16, 12, 8, 4][:], 1),
460 ([ 10, 15, 20, 30, 24, 18, 12, 6][:], 1),
461 ([ 8, 12, 16, 24, 20, 15, 10, 5][:], 1),
462 ([ 6, 9, 12, 18, 15, 12, 8, 4][:], 1),
463 ([ 4, 6, 8, 12, 10, 8, 6, 3][:], 1),
464 ([ 2, 3, 4, 6, 5, 4, 3, 2][:], 1),
468 ([ 2, 3, 2, 1][:], 1),
469 ([ 3, 6, 4, 2][:], 1),
470 ([ 4, 8, 6, 3][:], 1),
471 ([ 2, 4, 3, 2][:], 1),
480 var simple_roots = auto get_simple_roots(kc)
482 /* new_c = (a · simple_roots) / p */
483 var new_c = std.slalloc(simple_roots[0].c.len)
484 for var k = 0; k < new_c.len; ++k
485 new_c[k] = t.dup(zero)
488 for var j = 0; j < a.len; ++j
489 var mul = auto yakmo.QfromZ(a[j])
491 /* new_c += (a[j])(simple_roots[j]) */
492 for var k = 0; k < new_c.len; ++k
493 yakmo.gadd_ip(new_c[k], auto yakmo.rmul(mul, simple_roots[j].c[k]))
499 var r = auto yakmo.QfromZ(p)
502 for var k = 0; k < new_c.len; ++k
503 yakmo.rmul_ip(new_c[k], ri)
506 | _: /* I guarantee this won't happen, since user input doesn't touch here. */
509 std.slpush(&ret, std.mk([ .c = new_c ]))
516 All from Knapp. Return Π = { α_1, α_2, …, α_n}, with each vector
517 given as coefficients of the standard vector basis { e_i }. The
518 returned array (of the α_is) is 0-based. Each individual α_i is
521 const get_simple_roots = {kc
522 var ret : vector#[:] = [][:]
523 var zero : yakmo.Q# = yakmo.gid()
524 var one : yakmo.Q# = yakmo.rid()
525 var mone : yakmo.Q# = auto yakmo.gneg(one)
526 var half : yakmo.Q# = std.try(yakmo.Qfrom(1, 2))
527 var mhalf : yakmo.Q# = yakmo.gneg(half)
534 var new_c = std.slalloc((n : std.size) + 1)
535 var new_v = [ .c = new_c ]
536 for var j = 1; j <= n; ++j
537 std.slfill(new_c, zero)
538 new_c[(j - 1) + 0] = one
539 new_c[(j - 1) + 1] = mone
540 std.slpush(&ret, t.dup(&new_v))
544 var new_c = std.slalloc((n : std.size))
545 var new_v = [ .c = new_c ]
546 for var j = 1; j < n; ++j
547 std.slfill(new_c, zero)
548 new_c[(j - 1) + 0] = one
549 new_c[(j - 1) + 1] = mone
550 std.slpush(&ret, t.dup(&new_v))
552 std.slfill(new_c, zero)
554 std.slpush(&ret, t.dup(&new_v))
557 var new_c = std.slalloc((n : std.size))
558 var new_v = [ .c = new_c ]
559 for var j = 1; j < n; ++j
560 std.slfill(new_c, zero)
561 new_c[(j - 1) + 0] = one
562 new_c[(j - 1) + 1] = mone
563 std.slpush(&ret, t.dup(&new_v))
565 std.slfill(new_c, zero)
566 new_c[n - 1] = auto yakmo.gadd(one, one)
567 std.slpush(&ret, t.dup(&new_v))
571 -> get_simple_roots(`A 1)
574 var new_c = std.slalloc((n : std.size))
575 var new_v = [ .c = new_c ]
576 for var j = 1; j < n; ++j
577 std.slfill(new_c, zero)
578 new_c[(j - 1) + 0] = one
579 new_c[(j - 1) + 1] = mone
580 std.slpush(&ret, t.dup(&new_v))
582 std.slfill(new_c, zero)
589 std.slpush(&ret, t.dup(&new_v))
592 std.slpush(&ret, t.dup(&[ .c = [ half, mhalf, mhalf, mhalf, mhalf, mhalf, mhalf, half][:] ]))
593 std.slpush(&ret, t.dup(&[ .c = [ one, one, zero, zero, zero, zero, zero, zero][:] ]))
594 std.slpush(&ret, t.dup(&[ .c = [ mone, one, zero, zero, zero, zero, zero, zero][:] ]))
595 std.slpush(&ret, t.dup(&[ .c = [ zero, mone, one, zero, zero, zero, zero, zero][:] ]))
596 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, mone, one, zero, zero, zero, zero][:] ]))
597 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, mone, one, zero, zero, zero][:] ]))
599 std.slpush(&ret, t.dup(&[ .c = [ half, mhalf, mhalf, mhalf, mhalf, mhalf, mhalf, half][:] ]))
600 std.slpush(&ret, t.dup(&[ .c = [ one, one, zero, zero, zero, zero, zero, zero][:] ]))
601 std.slpush(&ret, t.dup(&[ .c = [ mone, one, zero, zero, zero, zero, zero, zero][:] ]))
602 std.slpush(&ret, t.dup(&[ .c = [ zero, mone, one, zero, zero, zero, zero, zero][:] ]))
603 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, mone, one, zero, zero, zero, zero][:] ]))
604 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, mone, one, zero, zero, zero][:] ]))
605 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, zero, mone, one, zero, zero][:] ]))
607 std.slpush(&ret, t.dup(&[ .c = [ half, mhalf, mhalf, mhalf, mhalf, mhalf, mhalf, half][:] ]))
608 std.slpush(&ret, t.dup(&[ .c = [ one, one, zero, zero, zero, zero, zero, zero][:] ]))
609 std.slpush(&ret, t.dup(&[ .c = [ mone, one, zero, zero, zero, zero, zero, zero][:] ]))
610 std.slpush(&ret, t.dup(&[ .c = [ zero, mone, one, zero, zero, zero, zero, zero][:] ]))
611 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, mone, one, zero, zero, zero, zero][:] ]))
612 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, mone, one, zero, zero, zero][:] ]))
613 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, zero, mone, one, zero, zero][:] ]))
614 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, zero, zero, mone, one, zero][:] ]))
616 std.slpush(&ret, t.dup(&[ .c = [ half, mhalf, mhalf, mhalf][:] ]))
617 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, zero, one][:] ]))
618 std.slpush(&ret, t.dup(&[ .c = [ zero, zero, one, mone][:] ]))
619 std.slpush(&ret, t.dup(&[ .c = [ zero, one, mone, zero][:] ]))
621 var mtwo = yakmo.QfromZ(-2)
622 std.slpush(&ret, t.dup(&[ .c = [ one, mone, zero][:] ]))
623 std.slpush(&ret, t.dup(&[ .c = [ mtwo, one, one][:] ]))
630 Return 0 or 1 such that the sum of bits of j + eventh_bit(j) is even.
631 For use with E6, E7, E8 only, which all use R⁸ as V, so we need only
632 check the first 7 bits.
634 var eventh_bit = {j : int
636 b += (j & 0b0000001 == 0) ? 0 : 1
637 b += (j & 0b0000010 == 0) ? 0 : 1
638 b += (j & 0b0000100 == 0) ? 0 : 1
639 b += (j & 0b0001000 == 0) ? 0 : 1
640 b += (j & 0b0010000 == 0) ? 0 : 1
641 b += (j & 0b0100000 == 0) ? 0 : 1
642 b += (j & 0b1000000 == 0) ? 0 : 1
647 const get_tree_partition = {t
648 var partition : weyl_reflection[:][:] = [][:]
651 std.slpush(&partition, [][:])
652 var nw : weyl_reflection = (n : weyl_reflection)
653 for var i = (n + 1) / 2; i >= 1; --i
654 var iw : weyl_reflection = (i : weyl_reflection)
656 std.slpush(&partition, std.sldup([ iw ][:]))
658 std.slpush(&partition, std.sldup([ iw, nw - iw + 1 ][:]))
664 var nw : weyl_reflection = (n : weyl_reflection)
666 -> get_tree_partition(`A n)
668 std.slpush(&partition, [][:])
669 for var i = 1; i <= (n - 2); ++i
670 var iw : weyl_reflection = (i : weyl_reflection)
671 std.slpush(&partition, std.sldup([ iw ][:]))
673 std.slpush(&partition, std.sldup([nw - 1, nw - 0][:]))
678 std.slpush(&partition, [][:])
679 std.slpush(&partition, std.sldup([ 2 ][:]))
680 std.slpush(&partition, std.sldup([ 4 ][:]))
681 std.slpush(&partition, std.sldup([ 3, 5 ][:]))
682 std.slpush(&partition, std.sldup([ 1, 6 ][:]))
685 std.slpush(&partition, [][:])
686 std.slpush(&partition, std.sldup([ 7 ][:]))
687 std.slpush(&partition, std.sldup([ 6 ][:]))
688 std.slpush(&partition, std.sldup([ 5 ][:]))
689 std.slpush(&partition, std.sldup([ 4 ][:]))
690 std.slpush(&partition, std.sldup([ 3, 2 ][:]))
691 std.slpush(&partition, std.sldup([ 1 ][:]))
694 std.slpush(&partition, [][:])
695 std.slpush(&partition, std.sldup([ 8 ][:]))
696 std.slpush(&partition, std.sldup([ 7 ][:]))
697 std.slpush(&partition, std.sldup([ 6 ][:]))
698 std.slpush(&partition, std.sldup([ 5 ][:]))
699 std.slpush(&partition, std.sldup([ 4 ][:]))
700 std.slpush(&partition, std.sldup([ 3, 2 ][:]))
701 std.slpush(&partition, std.sldup([ 1 ][:]))
705 std.slpush(&partition, [][:])
706 for var i = 1; i <= nn; ++i
707 var iw : weyl_reflection = (i : weyl_reflection)
708 std.slpush(&partition, std.sldup([ iw ][:]))
716 Get the weight for the vertex that will have j as the first subscript
717 (so j is 1-based, because this comes from names, not indices).
719 This returns both Dynkin diagram weights and quiver weights, which
720 are dual to each other. The return value is (quiver, dynkin)
722 const get_weight = {t : KC_type, j : weyl_reflection
723 if j <= 0 || (j : int) > get_n(t)
728 | (`A _, _): -> (1,1)
741 | (`D _, _): -> (1,1)
750 | (`F4, _): -> (0,0) /* impossible */
754 | (`G2, _): -> (0,0) /* impossible */
759 match std.htget(w0_stored, kc)
760 | `std.Some stored: -> stored
764 var w : weyl_reflection[:] = [][:]
768 for var l = n; l > 0; --l;
769 for var i = 1; i <= n; ++i
770 std.slpush(&w, (i : weyl_reflection))
774 var c = std.try(get_coxeter_elt(kc))
775 var h = coxeter_num(kc)
777 for var j = 0; j < hh; ++j
782 var c = std.try(get_coxeter_elt(kc))
783 var h = coxeter_num(kc)
785 for var j = 0; j < hh; ++j
790 std.htput(w0_stored, kc, w)
794 const inv_coxeter = {kc : KC_type, s : weyl_reflection
795 match get_coxeter_elt(kc)
797 for var j = 0; j < c.len; ++j