-
Notifications
You must be signed in to change notification settings - Fork 0
/
kiwi.lua
1267 lines (1110 loc) · 38.5 KB
/
kiwi.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- kiwi.lua - LuaJIT FFI bindings with C API fallback to kiwi constraint solver.
local ffi
do
local ffi_loader = package.preload["ffi"]
if ffi_loader == nil then
return require("ljkiwi")
end
ffi = ffi_loader() --[[@as ffilib]]
end
local kiwi = {}
local ljkiwi
local RUST = false
if not _G["KIWI_CKIWI"] then
local libpath = package.searchpath("rjkiwi", package.cpath)
if libpath then
RUST, ljkiwi = pcall(ffi.load, libpath)
if not RUST then
ljkiwi = nil
end
end
end
if not ljkiwi then
local cpath, err = package.searchpath("ljkiwi", package.cpath)
if cpath == nil then
error("kiwi dynamic library 'ljkiwi' not found\n" .. err)
end
ljkiwi = ffi.load(cpath)
end
do
ffi.cdef("void kiwi_solver_type_layout(unsigned sz_align[2]);")
local ti = ffi.new("unsigned[2]")
ljkiwi.kiwi_solver_type_layout(ti)
ffi.cdef(
"typedef struct KiwiSolver { unsigned char b_[$]; } __attribute__((aligned($))) KiwiSolver;",
ti[0],
ti[1]
)
end
ffi.cdef([[
typedef struct KiwiVar KiwiVar;
enum KiwiRelOp { LE, GE, EQ };
struct KiwiSmallStr {
size_t len_;
union {
char *ptr_;
char inline_[16];
};
};
struct KiwiVar {
size_t ref_count_;
double value_;
struct KiwiSmallStr name_;
};
KiwiVar* kiwi_var_new(const char* name);
void kiwi_var_free(KiwiVar* var);
const char* kiwi_var_name(const KiwiVar* var);
void kiwi_var_set_name(KiwiVar* var, const char* name);
void kiwi_var_set_value(KiwiVar* var, double value);
typedef struct KiwiTerm {
KiwiVar* var;
double coefficient;
} KiwiTerm;
typedef struct KiwiExpression {
double constant;
int term_count;
void* owner;
KiwiTerm terms_[?];
} KiwiExpression;
void kiwi_expression_retain(KiwiExpression* expr);
void kiwi_expression_destroy(KiwiExpression* expr);
void kiwi_expression_add_term(const KiwiExpression* expr, KiwiVar* var, double coeff, KiwiExpression* out);
void kiwi_expression_set_constant(const KiwiExpression* expr, double constant, KiwiExpression* out);
]])
if RUST then
ffi.cdef([[
typedef struct KiwiConstraint {
double constant;
int term_count;
enum KiwiRelOp op_;
double strength_;
void* owner;
KiwiTerm terms_[?];
} KiwiConstraint;
void kiwi_constraint_init(
KiwiConstraint* c,
const KiwiExpression* lhs,
const KiwiExpression* rhs,
enum KiwiRelOp op,
double strength
);
void kiwi_constraint_destroy(KiwiConstraint* c);
]])
else
ffi.cdef([[
typedef struct KiwiConstraint KiwiConstraint;
KiwiConstraint* kiwi_constraint_new(
const KiwiExpression* lhs,
const KiwiExpression* rhs,
enum KiwiRelOp op,
double strength
);
void kiwi_constraint_release(KiwiConstraint* c);
void kiwi_constraint_retain(KiwiConstraint* c);
double kiwi_constraint_strength(const KiwiConstraint* c);
enum KiwiRelOp kiwi_constraint_op(const KiwiConstraint* c);
]])
end
ffi.cdef([[
enum KiwiErrKind {
KiwiErrNone,
KiwiErrUnsatisfiableConstraint = 1,
KiwiErrUnknownConstraint,
KiwiErrDuplicateConstraint,
KiwiErrUnknownEditVar,
KiwiErrDuplicateEditVar,
KiwiErrBadRequiredStrength,
KiwiErrInternalSolverError,
KiwiErrAlloc,
KiwiErrNullObject,
KiwiErrUnknown,
};
typedef struct KiwiErr {
enum KiwiErrKind kind;
const char* message;
bool must_release;
} KiwiErr;
struct KiwiSolver;
void kiwi_str_release(char *);
void kiwi_err_release(const KiwiErr *);
bool kiwi_constraint_violated(const KiwiConstraint* c);
int kiwi_constraint_expression(KiwiConstraint* c, KiwiExpression* out, int out_size);
void kiwi_solver_init(KiwiSolver* s, unsigned error_mask);
void kiwi_solver_destroy(KiwiSolver* s);
unsigned kiwi_solver_get_error_mask(const KiwiSolver* s);
void kiwi_solver_set_error_mask(KiwiSolver* s, unsigned mask);
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraint* constraint);
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraint* constraint);
bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint);
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength);
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVar* var);
bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVar* var);
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVar* var, double value);
void kiwi_solver_update_vars(KiwiSolver* sp);
void kiwi_solver_reset(KiwiSolver* sp);
void kiwi_solver_dump(const KiwiSolver* sp);
char* kiwi_solver_dumps(const KiwiSolver* sp);
]])
local strformat = string.format
local ffi_copy, ffi_gc, ffi_istype, ffi_new, ffi_string =
ffi.copy, ffi.gc, ffi.istype, ffi.new, ffi.string
local concat = table.concat
local has_table_new, new_tab = pcall(require, "table.new")
if not has_table_new or type(new_tab) ~= "function" then
new_tab = function(_, _)
return {}
end
end
---@alias kiwi.ErrKind
---| '"KiwiErrNone"' # No error.
---| '"KiwiErrUnsatisfiableConstraint"' # The given constraint is required and cannot be satisfied.
---| '"KiwiErrUnknownConstraint"' # The given constraint has not been added to the solver.
---| '"KiwiErrDuplicateConstraint"' # The given constraint has already been added to the solver.
---| '"KiwiErrUnknownEditVar"' # The given edit variable has not been added to the solver.
---| '"KiwiErrDuplicateEditVar"' # The given edit variable has already been added to the solver.
---| '"KiwiErrBadRequiredStrength"' # The given strength is >= required.
---| '"KiwiErrInternalSolverError"' # An internal solver error occurred.
---| '"KiwiErrAlloc"' # A memory allocation error occurred.
---| '"KiwiErrNullObject"' # A method was invoked on a null or empty object.
---| '"KiwiErrUnknown"' # An unknown error occurred.
kiwi.ErrKind = ffi.typeof("enum KiwiErrKind") --[[@as kiwi.ErrKind]]
---@alias kiwi.RelOp
---| '"LE"' # <= (less than or equal)
---| '"GE"' # >= (greater than or equal)
---| '"EQ"' # == (equal)
kiwi.RelOp = ffi.typeof("enum KiwiRelOp")
kiwi.strength = {
REQUIRED = 1001001000.0,
STRONG = 1000000.0,
MEDIUM = 1000.0,
WEAK = 1.0,
}
local REQUIRED = kiwi.strength.REQUIRED
do
local function clamp(n)
return math.max(0, math.min(1000, n))
end
--- Create a custom constraint strength.
---@param a number: Scale factor 1e6
---@param b number: Scale factor 1e3
---@param c number: Scale factor 1
---@param w? number: Weight
---@return number
---@nodiscard
function kiwi.strength.create(a, b, c, w)
w = w or 1.0
return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w)
end
end
local Var = ffi.typeof("struct KiwiVar") --[[@as kiwi.Var]]
kiwi.Var = Var
function kiwi.is_var(o)
return ffi_istype(Var, o)
end
local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
local SIZEOF_TERM = assert(ffi.sizeof(Term))
kiwi.Term = Term
function kiwi.is_term(o)
return ffi_istype(Term, o)
end
local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]]
kiwi.Expression = Expression
function kiwi.is_expression(o)
return ffi_istype(Expression, o)
end
local Constraint = ffi.typeof("struct KiwiConstraint") --[[@as kiwi.Constraint]]
kiwi.Constraint = Constraint
function kiwi.is_constraint(o)
return ffi_istype(Constraint, o)
end
local new_constraint
if RUST then
---@return kiwi.Constraint
function new_constraint(lhs, rhs, op, strength)
local c = ffi_new(Constraint, (lhs and lhs.term_count or 0) + (rhs and rhs.term_count or 0))
ljkiwi.kiwi_constraint_init(c, lhs, rhs, op or "EQ", strength or REQUIRED)
return ffi_gc(c, ljkiwi.kiwi_constraint_destroy) --[[@as kiwi.Constraint]]
end
else
function new_constraint(lhs, rhs, op, strength)
return ffi_gc(
ljkiwi.kiwi_constraint_new(lhs, rhs, op or "EQ", strength or REQUIRED),
ljkiwi.kiwi_constraint_release
) --[[@as kiwi.Constraint]]
end
end
local function var_retain(var)
var.ref_count_ = var.ref_count_ + 1
end
local function var_release(var)
var.ref_count_ = var.ref_count_ - 1
if var.ref_count_ == 0 then
ljkiwi.kiwi_var_free(var)
end
end
---@param expr kiwi.Expression
---@param var kiwi.Var
---@param coeff number?
---@nodiscard
local function add_expr_term(expr, var, coeff)
local ret = ffi_new(Expression, expr.term_count + 1)
ljkiwi.kiwi_expression_add_term(expr, var, coeff or 1.0, ret)
return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
---@param constant number
---@param var kiwi.Var
---@param coeff number?
---@nodiscard
local function new_expr_one(constant, var, coeff)
local ret = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
local dt = ret.terms_[0]
dt.var = var
dt.coefficient = coeff or 1.0
ret.constant = constant
ret.term_count = 1
ret.owner = ret
var_retain(var)
return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
---@param constant number
---@param var1 kiwi.Var
---@param var2 kiwi.Var
---@param coeff1 number?
---@param coeff2 number?
---@nodiscard
local function new_expr_pair(constant, var1, var2, coeff1, coeff2)
local ret = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
local dt = ret.terms_[0]
dt.var = var1
dt.coefficient = coeff1 or 1.0
dt = ret.terms_[1]
dt.var = var2
dt.coefficient = coeff2 or 1.0
ret.constant = constant
ret.term_count = 2
ret.owner = ret
var_retain(var1)
var_retain(var2)
return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
local function typename(o)
if ffi.istype(Var, o) then
return "Var"
elseif ffi.istype(Term, o) then
return "Term"
elseif ffi.istype(Expression, o) then
return "Expression"
elseif ffi.istype(Constraint, o) then
return "Constraint"
else
return type(o)
end
end
local function op_error(a, b, op)
--stylua: ignore
-- level 3 works for arithmetic without TCO (no return), and for rel with TCO forced (explicit return)
error(strformat(
"invalid operand type for '%s' %.40s('%.99s') and %.40s('%.99s')",
op, typename(a), tostring(a), typename(b), tostring(b)), 3)
end
local OP_NAMES = {
LE = "<=",
GE = ">=",
EQ = "==",
}
local tmpexpr = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
local tmpexpr_r = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
local function toexpr(o, temp)
if ffi_istype(Expression, o) then
return o --[[@as kiwi.Expression]]
elseif type(o) == "number" then
temp.constant = o
temp.term_count = 0
return temp
end
temp.constant = 0
temp.term_count = 1
local t = temp.terms_[0]
if ffi_istype(Var, o) then
t.var = o --[[@as kiwi.Var]]
t.coefficient = 1.0
elseif ffi_istype(Term, o) then
ffi_copy(t, o, SIZEOF_TERM)
else
return nil
end
return temp
end
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param op kiwi.RelOp
---@param strength? number
---@nodiscard
local function rel(lhs, rhs, op, strength)
local el = toexpr(lhs, tmpexpr)
local er = toexpr(rhs, tmpexpr_r)
if el == nil or er == nil then
op_error(lhs, rhs, OP_NAMES[op])
end
return new_constraint(el, er, op, strength)
end
--- Define a constraint with expressions as `a <= b`.
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param strength? number
---@nodiscard
function kiwi.le(lhs, rhs, strength)
return rel(lhs, rhs, "LE", strength)
end
--- Define a constraint with expressions as `a >= b`.
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param strength? number
---@nodiscard
function kiwi.ge(lhs, rhs, strength)
return rel(lhs, rhs, "GE", strength)
end
--- Define a constraint with expressions as `a == b`.
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param strength? number
---@nodiscard
function kiwi.eq(lhs, rhs, strength)
return rel(lhs, rhs, "EQ", strength)
end
do
--- Variables are the values the constraint solver calculates.
---@class kiwi.Var: ffi.cdata*
---@field package value_ number
---@overload fun(name: string?): kiwi.Var
---@operator mul(number): kiwi.Term
---@operator div(number): kiwi.Term
---@operator unm: kiwi.Term
---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
local Var_cls = {
le = kiwi.le,
ge = kiwi.ge,
eq = kiwi.eq,
--- Change the name of the variable.
---@type fun(self: kiwi.Var, name: string)
set_name = ljkiwi.kiwi_var_set_name,
}
local Var_mt = {
__index = Var_cls,
}
function Var_mt:__new(name)
return ffi_gc(ljkiwi.kiwi_var_new(name)[0], var_release)
end
--- Get the name of the variable.
---@return string
---@nodiscard
function Var_cls:name()
return ffi_string(ljkiwi.kiwi_var_name(self))
end
--- Get the current value of the variable.
---@return number
---@nodiscard
function Var_cls:value()
return self.value_
end
--- Set the value of the variable.
---@param value number
function Var_cls:set(value)
self.value_ = value
end
--- Create a term from this variable.
---@param coefficient number?
---@return kiwi.Term
---@nodiscard
function Var_cls:toterm(coefficient)
return Term(self, coefficient)
end
--- Create a term from this variable.
---@param coefficient number?
---@param constant number?
---@return kiwi.Expression
---@nodiscard
function Var_cls:toexpr(coefficient, constant)
return new_expr_one(constant or 0.0, self, coefficient)
end
function Var_mt.__mul(a, b)
if type(a) == "number" then
return Term(b, a)
elseif type(b) == "number" then
return Term(a, b)
end
op_error(a, b, "*")
end
function Var_mt.__div(a, b)
if type(b) ~= "number" then
op_error(a, b, "/")
end
return Term(a, 1.0 / b)
end
function Var_mt:__unm()
return Term(self, -1.0)
end
function Var_mt.__add(a, b)
if ffi_istype(Var, b) then
if type(a) == "number" then
return new_expr_one(a, b)
else
return new_expr_pair(0.0, a, b)
end
elseif ffi_istype(Term, b) then
return new_expr_pair(0.0, a, b.var, 1.0, b.coefficient)
elseif ffi_istype(Expression, b) then
return add_expr_term(b, a)
elseif type(b) == "number" then
return new_expr_one(b, a)
end
op_error(a, b, "+")
end
function Var_mt.__sub(a, b)
return a + -b
end
function Var_mt:__tostring()
return self:name() .. "(" .. self:value() .. ")"
end
ffi.metatype(Var, Var_mt)
end
do
--- Terms are the components of an expression.
--- Each term is a variable multiplied by a constant coefficient (default 1.0).
---@class kiwi.Term: ffi.cdata*
---@overload fun(var: kiwi.Var, coefficient: number?): kiwi.Term
---@field var kiwi.Var
---@field coefficient number
---@operator mul(number): kiwi.Term
---@operator div(number): kiwi.Term
---@operator unm: kiwi.Term
---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
local Term_cls = {
le = kiwi.le,
ge = kiwi.ge,
eq = kiwi.eq,
}
---@return number
---@nodiscard
function Term_cls:value()
return self.coefficient * self.var:value()
end
--- Create an expression from this term.
---@param constant number?
---@return kiwi.Expression
function Term_cls:toexpr(constant)
return new_expr_one(constant or 0.0, self.var, self.coefficient)
end
local Term_mt = { __index = Term_cls }
local function term_release(term)
var_release(term.var)
end
function Term_mt.__new(T, var, coefficient)
local t = ffi_new(T) --[[@as kiwi.Term]]
t.var = var
t.coefficient = coefficient or 1.0
var_retain(var)
return ffi_gc(t, term_release)
end
function Term_mt.__mul(a, b)
if type(b) == "number" then
return Term(a.var, a.coefficient * b)
elseif type(a) == "number" then
return Term(b.var, b.coefficient * a)
end
op_error(a, b, "*")
end
function Term_mt.__div(a, b)
if type(b) ~= "number" then
op_error(a, b, "/")
end
return Term(a.var, a.coefficient / b)
end
function Term_mt:__unm()
return Term(self.var, -self.coefficient)
end
function Term_mt.__add(a, b)
if ffi_istype(Var, b) then
return new_expr_pair(0.0, a.var, b, a.coefficient)
elseif ffi_istype(Term, b) then
if type(a) == "number" then
return new_expr_one(a, b.var, b.coefficient)
else
return new_expr_pair(0.0, a.var, b.var, a.coefficient, b.coefficient)
end
elseif ffi_istype(Expression, b) then
return add_expr_term(b, a.var, a.coefficient)
elseif type(b) == "number" then
return new_expr_one(b, a.var, a.coefficient)
end
op_error(a, b, "+")
end
function Term_mt.__sub(a, b)
return Term_mt.__add(a, -b)
end
function Term_mt:__tostring()
return tostring(self.coefficient) .. " " .. self.var:name()
end
ffi.metatype(Term, Term_mt)
end
do
--- Expressions are a sum of terms with an added constant.
---@class kiwi.Expression: ffi.cdata*
---@overload fun(constant: number, ...: kiwi.Term): kiwi.Expression
---@field constant number
---@field package owner ffi.cdata*
---@field package term_count number
---@field package terms_ ffi.cdata*
---@operator mul(number): kiwi.Expression
---@operator div(number): kiwi.Expression
---@operator unm: kiwi.Expression
---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression
local Expression_cls = {
le = kiwi.le,
ge = kiwi.ge,
eq = kiwi.eq,
}
---@param expr kiwi.Expression
---@param constant number
---@nodiscard
local function mul_expr_coeff(expr, constant)
local ret = ffi_new(Expression, expr.term_count) --[[@as kiwi.Expression]]
for i = 0, expr.term_count - 1 do
local st = expr.terms_[i] --[[@as kiwi.Term]]
local dt = ret.terms_[i] --[[@as kiwi.Term]]
dt.var = st.var
dt.coefficient = st.coefficient * constant
end
ret.constant = expr.constant * constant
ret.term_count = expr.term_count
ljkiwi.kiwi_expression_retain(ret)
return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
---@param a kiwi.Expression
---@param b kiwi.Expression
---@nodiscard
local function add_expr_expr(a, b)
local a_count = a.term_count
local b_count = b.term_count
local ret = ffi_new(Expression, a_count + b_count) --[[@as kiwi.Expression]]
ffi_copy(ret.terms_, a.terms_, SIZEOF_TERM * a_count)
ffi_copy(ret.terms_ + a_count, b.terms_, SIZEOF_TERM * b_count)
ret.constant = a.constant + b.constant
ret.term_count = a_count + b_count
ljkiwi.kiwi_expression_retain(ret)
return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
---@param expr kiwi.Expression
---@param constant number
---@nodiscard
local function new_expr_constant(expr, constant)
local ret = ffi_new(Expression, expr.term_count)
ljkiwi.kiwi_expression_set_constant(expr, constant, ret)
return ffi_gc(ret, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
---@return number
---@nodiscard
function Expression_cls:value()
local sum = self.constant
for i = 0, self.term_count - 1 do
local t = self.terms_[i]
sum = sum + t.var:value() * t.coefficient
end
return sum
end
---@return kiwi.Term[]
---@nodiscard
function Expression_cls:terms()
local terms = new_tab(self.term_count, 0)
for i = 0, self.term_count - 1 do
local t = self.terms_[i] --[[@as kiwi.Term]]
terms[i + 1] = Term(t.var, t.coefficient)
end
return terms
end
---@return kiwi.Expression
---@nodiscard
function Expression_cls:copy()
return new_expr_constant(self, self.constant)
end
local Expression_mt = {
__index = Expression_cls,
}
function Expression_mt:__new(constant, ...)
local term_count = select("#", ...)
local e = ffi_new(self, term_count) --[[@as kiwi.Expression]]
e.term_count = term_count
e.constant = constant
for i = 1, term_count do
local t = select(i, ...)
local dt = e.terms_[i - 1] --[[@as kiwi.Term]]
dt.var = t.var
dt.coefficient = t.coefficient
end
ljkiwi.kiwi_expression_retain(e)
return ffi_gc(e, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
function Expression_mt.__mul(a, b)
if type(a) == "number" then
return mul_expr_coeff(b, a)
elseif type(b) == "number" then
return mul_expr_coeff(a, b)
end
op_error(a, b, "*")
end
function Expression_mt.__div(a, b)
if type(b) ~= "number" then
op_error(a, b, "/")
end
return mul_expr_coeff(a, 1.0 / b)
end
function Expression_mt:__unm()
return mul_expr_coeff(self, -1.0)
end
function Expression_mt.__add(a, b)
if ffi_istype(Var, b) then
return add_expr_term(a, b)
elseif ffi_istype(Expression, b) then
if type(a) == "number" then
return new_expr_constant(b, a + b.constant)
else
return add_expr_expr(a, b)
end
elseif ffi_istype(Term, b) then
return add_expr_term(a, b.var, b.coefficient)
elseif type(b) == "number" then
return new_expr_constant(a, a.constant + b)
end
op_error(a, b, "+")
end
function Expression_mt.__sub(a, b)
return Expression_mt.__add(a, -b)
end
function Expression_mt:__tostring()
local tab = new_tab(self.term_count + 1, 0)
for i = 0, self.term_count - 1 do
local t = self.terms_[i]
tab[i + 1] = tostring(t.coefficient) .. " " .. t.var:name()
end
tab[self.term_count + 1] = self.constant
return concat(tab, " + ")
end
ffi.metatype(Expression, Expression_mt)
end
do
--- A constraint is a linear inequality or equality with associated strength.
--- Constraints can be built with arbitrary left and right hand expressions. But
--- ultimately they all have the form `expression [op] 0`.
---@class kiwi.Constraint: ffi.cdata*
---@field package constant number
---@field package term_count number
---@field package owner ffi.cdata*
---@field package op_ kiwi.RelOp
---@field package strength_ number
---@field package terms_ ffi.cdata*
---@overload fun(lhs: kiwi.Expression?, rhs: kiwi.Expression?, op: kiwi.RelOp?, strength: number?): kiwi.Constraint
local Constraint_cls = {
--- Whether the constraint is violated in the current solution.
---@type fun(self: kiwi.Constraint): boolean
violated = ljkiwi.kiwi_constraint_violated,
}
if RUST then
--- The strength of the constraint.
---@return number
---@nodiscard
function Constraint_cls:strength()
return self.strength_
end
--- The relational operator of the constraint.
---@return kiwi.RelOp
---@nodiscard
function Constraint_cls:op()
return self.op_
end
else
Constraint_cls.strength = ljkiwi.kiwi_constraint_strength
Constraint_cls.op = ljkiwi.kiwi_constraint_op
end
--- The reduced expression defining the constraint.
---@return kiwi.Expression
---@nodiscard
function Constraint_cls:expression()
local SZ = 7
local expr = ffi_new(Expression, SZ) --[[@as kiwi.Expression]]
local n = ljkiwi.kiwi_constraint_expression(self, expr, SZ)
if n > SZ then
expr = ffi_new(Expression, n) --[[@as kiwi.Expression]]
n = ljkiwi.kiwi_constraint_expression(self, expr, n)
end
return ffi_gc(expr, ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
end
--- Add the constraint to the solver.
--- Raises:
--- KiwiErrDuplicateConstraint: The given constraint has already been added to the solver.
--- KiwiErrUnsatisfiableConstraint: The given constraint is required and cannot be satisfied.
---@param solver kiwi.Solver
---@return kiwi.Constraint
function Constraint_cls:add_to(solver)
solver:add_constraint(self)
return self
end
--- Remove the constraint from the solver.
--- Raises:
--- KiwiErrUnknownConstraint: The given constraint has not been added to the solver.
---@param solver kiwi.Solver
---@return kiwi.Constraint
function Constraint_cls:remove_from(solver)
solver:remove_constraint(self)
return self
end
local Constraint_mt = {
__index = Constraint_cls,
}
function Constraint_mt:__new(lhs, rhs, op, strength)
return new_constraint(lhs, rhs, op, strength)
end
local OPS = { [0] = "<=", ">=", "==" }
local STRENGTH_NAMES = {
[kiwi.strength.REQUIRED] = "required",
[kiwi.strength.STRONG] = "strong",
[kiwi.strength.MEDIUM] = "medium",
[kiwi.strength.WEAK] = "weak",
}
function Constraint_mt:__tostring()
local strength = self:strength()
local strength_str = STRENGTH_NAMES[strength] or tostring(strength)
local op = OPS[tonumber(self:op())]
return strformat("%s %s 0 | %s", tostring(self:expression()), op, strength_str)
end
ffi.metatype(Constraint, Constraint_mt)
end
do
local constraints = {}
kiwi.constraints = constraints
--- Create a constraint between a pair of variables with ratio.
--- The constraint is of the form `left [op|==] coeff right + [constant|0.0]`.
---@param left kiwi.Var
---@param coeff number right side term coefficient
---@param right kiwi.Var
---@param constant number? constant (default 0.0)
---@param op kiwi.RelOp? relational operator (default "EQ")
---@param strength number? strength (default REQUIRED)
---@return kiwi.Constraint
---@nodiscard
function constraints.pair_ratio(left, coeff, right, constant, op, strength)
assert(ffi_istype(Var, left) and ffi_istype(Var, right))
local dt = tmpexpr.terms_[0]
dt.var = left
dt.coefficient = 1.0
dt = tmpexpr.terms_[1]
dt.var = right
dt.coefficient = -coeff
tmpexpr.constant = constant ~= nil and constant or 0
tmpexpr.term_count = 2
return new_constraint(tmpexpr, nil, op, strength)
end
local pair_ratio = constraints.pair_ratio
--- Create a constraint between a pair of variables with offset.
--- The constraint is of the form `left [op|==] right + [constant|0.0]`.
---@param left kiwi.Var
---@param right kiwi.Var
---@param constant number? constant (default 0.0)
---@param op kiwi.RelOp? relational operator (default "EQ")
---@param strength number? strength (default REQUIRED)
---@return kiwi.Constraint
---@nodiscard
function constraints.pair(left, right, constant, op, strength)
return pair_ratio(left, 1.0, right, constant, op, strength)
end
--- Create a single term constraint
--- The constraint is of the form `var [op|==] [constant|0.0]`.
---@param var kiwi.Var
---@param constant number? constant (default 0.0)
---@param op kiwi.RelOp? relational operator (default "EQ")
---@param strength number? strength (default REQUIRED)
---@return kiwi.Constraint
---@nodiscard
function constraints.single(var, constant, op, strength)
assert(ffi_istype(Var, var))
tmpexpr.constant = -(constant or 0)
tmpexpr.term_count = 1
local t = tmpexpr.terms_[0]
t.var = var
t.coefficient = 1.0
return new_constraint(tmpexpr, nil, op, strength)
end
end
do
local bit = require("bit")
local band, bor, lshift = bit.band, bit.bor, bit.lshift
--- Produce a custom error raise mask
--- Error kinds specified in the mask will not cause a lua
--- error to be raised.
---@param kinds (kiwi.ErrKind|integer)[]
---@param invert boolean?
---@return integer
function kiwi.error_mask(kinds, invert)
local mask = 0
for _, k in ipairs(kinds) do
mask = bor(mask, lshift(1, kiwi.ErrKind(k)))
end
return invert and bit.bnot(mask) or mask
end
kiwi.ERROR_MASK_ALL = 0xFFFF
--- an error mask that raises errors only for fatal conditions
kiwi.ERROR_MASK_NON_FATAL = bit.bnot(kiwi.error_mask({
"KiwiErrInternalSolverError",
"KiwiErrAlloc",
"KiwiErrNullObject",
"KiwiErrUnknown",
}))
---@class kiwi.KiwiErr: ffi.cdata*
---@field package kind kiwi.ErrKind
---@field package message ffi.cdata*
---@field package must_release boolean
---@overload fun(): kiwi.KiwiErr
local KiwiErr = ffi.typeof("struct KiwiErr") --[[@as kiwi.KiwiErr]]