lambda-calculus 3.1.0__py3-none-any.whl → 3.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,8 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Implementation of the Lambda calculus"""
4
2
 
5
- from .terms import Variable, Abstraction, Application
3
+ from ._version import version as __version__ # noqa: F401
4
+ from .terms import Abstraction, Application, Variable
6
5
 
7
- __version__ = "3.1.0"
8
- __author__ = "Eric Niklas Wolf"
9
- __email__ = "eric_niklas.wolf@mailbox.tu-dresden.de"
10
- __all__ = (
11
- "Variable",
12
- "Abstraction",
13
- "Application",
14
- "terms",
15
- "visitors",
16
- "errors"
17
- )
6
+ __author__ = "Eric Niklas Wolf"
7
+ __email__ = "eric_niklas.wolf@mailbox.tu-dresden.de"
8
+ __all__ = ("Abstraction", "Application", "Variable", "errors", "terms", "visitors")
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '3.1.2'
22
+ __version_tuple__ = version_tuple = (3, 1, 2)
23
+
24
+ __commit_id__ = commit_id = None
lambda_calculus/errors.py CHANGED
@@ -1,14 +1,11 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Errors raised by Term operations"""
4
2
 
5
3
  from __future__ import annotations
4
+
6
5
  from collections.abc import Collection
7
- from typing import TypeVar, Generic
6
+ from typing import Generic, TypeVar
8
7
 
9
- __all__ = (
10
- "CollisionError",
11
- )
8
+ __all__ = ("CollisionError",)
12
9
 
13
10
  V = TypeVar("V")
14
11
 
@@ -1,27 +1,27 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Lambda Terms"""
4
2
 
5
3
  from __future__ import annotations
4
+
6
5
  from abc import abstractmethod
7
- from collections.abc import Sequence, Set, Iterable, Iterator
6
+ from collections.abc import Iterable, Iterator, Sequence
7
+ from collections.abc import Set as AbstractSet
8
8
  from dataclasses import dataclass
9
- from typing import TypeVar, final
10
- from .. import visitors
9
+ from typing import Self, TypeVar, final
10
+
11
11
  from ..errors import CollisionError
12
- from ..visitors import walking
12
+ from ..visitors import Visitor, walking
13
13
  from ..visitors.substitution import checked
14
14
 
15
15
  __all__ = (
16
- "Term",
17
- "Variable",
18
16
  "Abstraction",
19
17
  "Application",
18
+ "Term",
19
+ "Variable",
20
20
  "abc",
21
21
  "arithmetic",
22
+ "combinators",
22
23
  "logic",
23
24
  "pairs",
24
- "combinators"
25
25
  )
26
26
 
27
27
  T = TypeVar("T")
@@ -52,25 +52,25 @@ class Term(Iterable["Term[V]"]):
52
52
 
53
53
  :return: lambda term string
54
54
  """
55
- raise NotImplementedError()
55
+ raise NotImplementedError
56
56
 
57
57
  @abstractmethod
58
- def free_variables(self) -> Set[V]:
58
+ def free_variables(self) -> AbstractSet[V]:
59
59
  """
60
60
  Calculate the free variables of this Term.
61
61
 
62
62
  :return: variables not bound by an abstraction
63
63
  """
64
- raise NotImplementedError()
64
+ raise NotImplementedError
65
65
 
66
66
  @abstractmethod
67
- def bound_variables(self) -> Set[V]:
67
+ def bound_variables(self) -> AbstractSet[V]:
68
68
  """
69
69
  Calculate the bound variables of this Term.
70
70
 
71
71
  :return: variables bound by an abstraction
72
72
  """
73
- raise NotImplementedError()
73
+ raise NotImplementedError
74
74
 
75
75
  @abstractmethod
76
76
  def is_beta_normal_form(self) -> bool:
@@ -79,17 +79,17 @@ class Term(Iterable["Term[V]"]):
79
79
 
80
80
  :return: if no beta reductions can be performed
81
81
  """
82
- raise NotImplementedError()
82
+ raise NotImplementedError
83
83
 
84
84
  @abstractmethod
85
- def accept(self, visitor: visitors.Visitor[T, V]) -> T:
85
+ def accept(self, visitor: Visitor[T, V]) -> T:
86
86
  """
87
87
  Accept a visitor by calling his corresponding method.
88
88
 
89
89
  :param visitor: Visitor to accept
90
90
  :return: value returned by the visitors corresponding method
91
91
  """
92
- raise NotImplementedError()
92
+ raise NotImplementedError
93
93
 
94
94
  def abstract(self, *variables: V) -> Abstraction[V]:
95
95
  """
@@ -141,7 +141,7 @@ class Variable(Term[V]):
141
141
  name: V
142
142
 
143
143
  @classmethod
144
- def with_valid_name(cls, name: V) -> Variable[V]:
144
+ def with_valid_name(cls, name: V) -> Self:
145
145
  """
146
146
  Create an instance with a valid name.
147
147
 
@@ -151,10 +151,12 @@ class Variable(Term[V]):
151
151
  """
152
152
  string = str(name)
153
153
  if not string:
154
- raise ValueError("empty string representation")
154
+ msg = "empty string representation"
155
+ raise ValueError(msg)
155
156
  for character in string:
156
157
  if character in "().λ" or character.isspace():
157
- raise ValueError(f"invalid character: '{character}'")
158
+ msg = f"invalid character: '{character}'"
159
+ raise ValueError(msg)
158
160
  return cls(name)
159
161
 
160
162
  def __str__(self) -> str:
@@ -165,7 +167,7 @@ class Variable(Term[V]):
165
167
  """
166
168
  return str(self.name)
167
169
 
168
- def free_variables(self) -> Set[V]:
170
+ def free_variables(self) -> AbstractSet[V]:
169
171
  """
170
172
  Calculate the free variables of this Term.
171
173
 
@@ -173,7 +175,7 @@ class Variable(Term[V]):
173
175
  """
174
176
  return {self.name}
175
177
 
176
- def bound_variables(self) -> Set[V]:
178
+ def bound_variables(self) -> AbstractSet[V]:
177
179
  """
178
180
  Calculate the bound variables of this Term.
179
181
 
@@ -189,7 +191,7 @@ class Variable(Term[V]):
189
191
  """
190
192
  return True
191
193
 
192
- def accept(self, visitor: visitors.Visitor[T, V]) -> T:
194
+ def accept(self, visitor: Visitor[T, V]) -> T:
193
195
  """
194
196
  Accept a visitor by calling visitors.Visitor.visit_variable.
195
197
 
@@ -214,7 +216,7 @@ class Abstraction(Term[V]):
214
216
  body: Term[V]
215
217
 
216
218
  @classmethod
217
- def curried(cls, variables: Sequence[V], body: Term[V]) -> Abstraction[V]:
219
+ def curried(cls, variables: Sequence[V], body: Term[V]) -> Self:
218
220
  """
219
221
  Create an Abstraction binding multiple variables.
220
222
 
@@ -230,7 +232,8 @@ class Abstraction(Term[V]):
230
232
  term = cls(variable, term)
231
233
  return term
232
234
  case _:
233
- raise ValueError("no variables to bind")
235
+ msg = "no variables to bind"
236
+ raise ValueError(msg)
234
237
 
235
238
  def __str__(self) -> str:
236
239
  """
@@ -240,7 +243,7 @@ class Abstraction(Term[V]):
240
243
  """
241
244
  return f"(λ{self.bound}.{self.body})"
242
245
 
243
- def free_variables(self) -> Set[V]:
246
+ def free_variables(self) -> AbstractSet[V]:
244
247
  """
245
248
  Calculate the free variables of this Term.
246
249
 
@@ -248,7 +251,7 @@ class Abstraction(Term[V]):
248
251
  """
249
252
  return self.body.free_variables() - {self.bound}
250
253
 
251
- def bound_variables(self) -> Set[V]:
254
+ def bound_variables(self) -> AbstractSet[V]:
252
255
  """
253
256
  Calculate the free variables of this Term.
254
257
 
@@ -274,12 +277,10 @@ class Abstraction(Term[V]):
274
277
  """
275
278
  if new == self.bound:
276
279
  return self
277
- elif new not in self.body.free_variables():
278
- return Abstraction(
279
- new,
280
- self.body.substitute(self.bound, Variable(new))
281
- )
282
- raise CollisionError("new variable would bind free variable in body", (new,))
280
+ if new not in self.body.free_variables():
281
+ return Abstraction(new, self.body.substitute(self.bound, Variable(new)))
282
+ msg = "new variable would bind free variable in body"
283
+ raise CollisionError(msg, (new,))
283
284
 
284
285
  def eta_reduction(self) -> Term[V]:
285
286
  """
@@ -292,9 +293,10 @@ class Abstraction(Term[V]):
292
293
  case Application(f, Variable(x)) if x == self.bound and x not in f.free_variables():
293
294
  return f
294
295
  case _:
295
- raise ValueError("abstraction is not useless")
296
+ msg = "abstraction is not useless"
297
+ raise ValueError(msg)
296
298
 
297
- def accept(self, visitor: visitors.Visitor[T, V]) -> T:
299
+ def accept(self, visitor: Visitor[T, V]) -> T:
298
300
  """
299
301
  Accept a visitor by calling visitors.Visitor.visit_abstraction.
300
302
 
@@ -312,8 +314,7 @@ class Abstraction(Term[V]):
312
314
  :return: new term
313
315
  """
314
316
  return Abstraction(
315
- self.bound if bound is None else bound,
316
- self.body if body is None else body
317
+ self.bound if bound is None else bound, self.body if body is None else body
317
318
  )
318
319
 
319
320
 
@@ -332,7 +333,7 @@ class Application(Term[V]):
332
333
  argument: Term[V]
333
334
 
334
335
  @classmethod
335
- def with_arguments(cls, abstraction: Term[V], arguments: Sequence[Term[V]]) -> Application[V]:
336
+ def with_arguments(cls, abstraction: Term[V], arguments: Sequence[Term[V]]) -> Self:
336
337
  """
337
338
  Create an Application applying the abstraction to multiple arguments.
338
339
 
@@ -348,7 +349,8 @@ class Application(Term[V]):
348
349
  term = cls(term, argument)
349
350
  return term
350
351
  case _:
351
- raise ValueError("no arguments to apply abstraction to")
352
+ msg = "no arguments to apply abstraction to"
353
+ raise ValueError(msg)
352
354
 
353
355
  def __str__(self) -> str:
354
356
  """
@@ -358,7 +360,7 @@ class Application(Term[V]):
358
360
  """
359
361
  return f"({self.abstraction} {self.argument})"
360
362
 
361
- def free_variables(self) -> Set[V]:
363
+ def free_variables(self) -> AbstractSet[V]:
362
364
  """
363
365
  Calculate the free variables of this Term.
364
366
 
@@ -366,7 +368,7 @@ class Application(Term[V]):
366
368
  """
367
369
  return self.abstraction.free_variables() | self.argument.free_variables()
368
370
 
369
- def bound_variables(self) -> Set[V]:
371
+ def bound_variables(self) -> AbstractSet[V]:
370
372
  """
371
373
  Calculate the free variables of this Term.
372
374
 
@@ -388,9 +390,11 @@ class Application(Term[V]):
388
390
 
389
391
  :return: if no beta reductions can be performed
390
392
  """
391
- return not self.is_redex() \
392
- and self.abstraction.is_beta_normal_form() \
393
+ return (
394
+ not self.is_redex()
395
+ and self.abstraction.is_beta_normal_form()
393
396
  and self.argument.is_beta_normal_form()
397
+ )
394
398
 
395
399
  def beta_reduction(self) -> Term[V]:
396
400
  """
@@ -403,9 +407,10 @@ class Application(Term[V]):
403
407
  case Abstraction(bound, body):
404
408
  return body.substitute(bound, self.argument)
405
409
  case _:
406
- raise ValueError("can not perform reduction without known Abstraction")
410
+ msg = "can not perform reduction without known Abstraction"
411
+ raise ValueError(msg)
407
412
 
408
- def accept(self, visitor: visitors.Visitor[T, V]) -> T:
413
+ def accept(self, visitor: Visitor[T, V]) -> T:
409
414
  """
410
415
  Accept a visitor by calling visitors.Visitor.visit_application.
411
416
 
@@ -414,7 +419,9 @@ class Application(Term[V]):
414
419
  """
415
420
  return visitor.visit_application(self)
416
421
 
417
- def replace(self, *, abstraction: Term[V] | None = None, argument: Term[V] | None = None) -> Application[V]:
422
+ def replace(
423
+ self, *, abstraction: Term[V] | None = None, argument: Term[V] | None = None
424
+ ) -> Application[V]:
418
425
  """
419
426
  Return a copy with specific attributes replaced.
420
427
 
@@ -424,5 +431,5 @@ class Application(Term[V]):
424
431
  """
425
432
  return Application(
426
433
  self.abstraction if abstraction is None else abstraction,
427
- self.argument if argument is None else argument
434
+ self.argument if argument is None else argument,
428
435
  )
@@ -1,13 +1,13 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Predefined Variables for all ASCII letters"""
4
2
 
5
3
  from string import ascii_letters
4
+
6
5
  from . import Variable
7
6
 
8
7
  __all__ = tuple(ascii_letters)
9
8
 
10
9
  # better IDE autocompletion
10
+ # ruff: noqa: E741
11
11
  a, b, c, d, e, f, g, h, i, j = map(Variable, "abcdefghij")
12
12
  k, l, m, n, o, p, q, r, s, t = map(Variable, "klmnopqrst")
13
13
  u, v, w, x, y, z = map(Variable, "uvwxyz")
@@ -1,21 +1,11 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Implementations of natural numbers and arithmetic operators"""
4
2
 
5
3
  from typing import Final
6
- from . import Term, Variable, Abstraction, Application
7
- from .logic import TRUE, FALSE
8
4
 
9
- __all__ = (
10
- "ISZERO",
11
- "SUCCESSOR",
12
- "PREDECESSOR",
13
- "ADD",
14
- "SUBTRACT",
15
- "MULTIPLY",
16
- "POWER",
17
- "number"
18
- )
5
+ from . import Abstraction, Application, Term, Variable
6
+ from .logic import FALSE, TRUE
7
+
8
+ __all__ = ("ADD", "ISZERO", "MULTIPLY", "POWER", "PREDECESSOR", "SUBTRACT", "SUCCESSOR", "number")
19
9
 
20
10
  ISZERO: Final = Variable("n").apply_to(FALSE.abstract("x"), TRUE).abstract("n")
21
11
  """
@@ -26,12 +16,8 @@ if its argument is zero, :const:`lambda_calculus.terms.logic.FALSE` otherwise
26
16
  SUCCESSOR: Final = Abstraction.curried(
27
17
  ("n", "f", "x"),
28
18
  Application(
29
- Variable("f"),
30
- Application.with_arguments(
31
- Variable("n"),
32
- (Variable("f"), Variable("x"))
33
- )
34
- )
19
+ Variable("f"), Application.with_arguments(Variable("n"), (Variable("f"), Variable("x")))
20
+ ),
35
21
  )
36
22
  """
37
23
  Term evaluating to its argument incremented by one.
@@ -43,19 +29,12 @@ PREDECESSOR: Final = Abstraction.curried(
43
29
  Variable("n"),
44
30
  (
45
31
  Abstraction.curried(
46
- ("g", "h"),
47
- Application(
48
- Variable("h"),
49
- Application(
50
- Variable("g"),
51
- Variable("f")
52
- )
53
- )
32
+ ("g", "h"), Application(Variable("h"), Application(Variable("g"), Variable("f")))
54
33
  ),
55
34
  Abstraction("u", Variable("x")),
56
35
  Abstraction("u", Variable("u")),
57
- )
58
- )
36
+ ),
37
+ ),
59
38
  )
60
39
  """
61
40
  Term ealuating to its argument decremented by one.
@@ -65,51 +44,28 @@ ADD: Final = Abstraction.curried(
65
44
  ("m", "n", "f", "x"),
66
45
  Application.with_arguments(
67
46
  Variable("m"),
68
- (
69
- Variable("f"),
70
- Application.with_arguments(
71
- Variable("n"),
72
- (Variable("f"), Variable("x"))
73
- )
74
- )
75
- )
47
+ (Variable("f"), Application.with_arguments(Variable("n"), (Variable("f"), Variable("x")))),
48
+ ),
76
49
  )
77
50
  """
78
51
  Term evaluating to the sum of its two arguments.
79
52
  """
80
53
 
81
54
  SUBTRACT: Final = Abstraction.curried(
82
- ("m", "n"),
83
- Application.with_arguments(
84
- Variable("n"),
85
- (PREDECESSOR, Variable("m"))
86
- )
55
+ ("m", "n"), Application.with_arguments(Variable("n"), (PREDECESSOR, Variable("m")))
87
56
  )
88
57
  """
89
58
  Term evaluating to the difference of its two arguments.
90
59
  """
91
60
 
92
61
  MULTIPLY: Final = Abstraction.curried(
93
- ("m", "n", "f"),
94
- Application(
95
- Variable("m"),
96
- Application(
97
- Variable("n"),
98
- Variable("f")
99
- )
100
- )
62
+ ("m", "n", "f"), Application(Variable("m"), Application(Variable("n"), Variable("f")))
101
63
  )
102
64
  """
103
65
  Term evaluating to the product of its two arguments.
104
66
  """
105
67
 
106
- POWER: Final = Abstraction.curried(
107
- ("b", "e"),
108
- Application(
109
- Variable("e"),
110
- Variable("b")
111
- )
112
- )
68
+ POWER: Final = Abstraction.curried(("b", "e"), Application(Variable("e"), Variable("b")))
113
69
  """
114
70
  Term evaluating to its first argument to the power of its second argument.
115
71
  """
@@ -124,12 +80,10 @@ def number(n: int) -> Abstraction[str]:
124
80
  :return: requested term
125
81
  """
126
82
  if n < 0:
127
- raise ValueError("number is not natural")
83
+ msg = "number is not natural"
84
+ raise ValueError(msg)
128
85
  f = Variable("f")
129
86
  body: Term[str] = Variable("x")
130
87
  for _ in range(n):
131
88
  body = Application(f, body)
132
- return Abstraction.curried(
133
- ("f", "x"),
134
- body
135
- )
89
+ return Abstraction.curried(("f", "x"), body)
@@ -1,38 +1,24 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Common combinators"""
4
2
 
5
3
  from typing import Final
6
- from . import Variable, Application
7
4
 
8
- __all__ = (
9
- "Y",
10
- "S",
11
- "K",
12
- "I",
13
- "B",
14
- "C",
15
- "W",
16
- "DELTA",
17
- "OMEGA"
18
- )
5
+ from . import Application, Variable
6
+
7
+ __all__ = ("DELTA", "OMEGA", "B", "C", "I", "K", "S", "W", "Y")
19
8
 
20
9
  Y: Final = Application(
21
- Variable("g").apply_to(
22
- Variable("x").apply_to(Variable("x"))
23
- ).abstract("x"),
24
- Variable("g").apply_to(
25
- Variable("x").apply_to(Variable("x"))
26
- ).abstract("x")
10
+ Variable("g").apply_to(Variable("x").apply_to(Variable("x"))).abstract("x"),
11
+ Variable("g").apply_to(Variable("x").apply_to(Variable("x"))).abstract("x"),
27
12
  ).abstract("g")
28
13
  """
29
14
  Y combinator used to define recursive terms.
30
15
  """
31
16
 
32
- S: Final = Variable("x").apply_to(
33
- Variable("z"),
34
- Variable("y").apply_to(Variable("z"))
35
- ).abstract("x", "y", "z")
17
+ S: Final = (
18
+ Variable("x")
19
+ .apply_to(Variable("z"), Variable("y").apply_to(Variable("z")))
20
+ .abstract("x", "y", "z")
21
+ )
36
22
  """
37
23
  S combinator of the SKI combinator calculus.
38
24
  """
@@ -42,37 +28,27 @@ K: Final = Variable("x").abstract("x", "y")
42
28
  K combinator of the SKI combinator calculus.
43
29
  """
44
30
 
45
- I: Final = Variable("x").abstract("x")
31
+ I: Final = Variable("x").abstract("x") # noqa: E741
46
32
  """
47
33
  I combinator of the SKI combinator calculus.
48
34
  """
49
35
 
50
- B: Final = Variable("x").apply_to(
51
- Variable("y").apply_to(Variable("z"))
52
- ).abstract("x", "y", "z")
36
+ B: Final = Variable("x").apply_to(Variable("y").apply_to(Variable("z"))).abstract("x", "y", "z")
53
37
  """
54
38
  B combinator of the BCKW combinator calculus.
55
39
  """
56
40
 
57
- C: Final = Variable("x").apply_to(
58
- Variable("z"),
59
- Variable("y")
60
- ).abstract("x", "y", "z")
41
+ C: Final = Variable("x").apply_to(Variable("z"), Variable("y")).abstract("x", "y", "z")
61
42
  """
62
43
  C combinator of the BCKW combinator calculus.
63
44
  """
64
45
 
65
- W: Final = Variable("x").apply_to(
66
- Variable("y"),
67
- Variable("y")
68
- ).abstract("x", "y")
46
+ W: Final = Variable("x").apply_to(Variable("y"), Variable("y")).abstract("x", "y")
69
47
  """
70
48
  W combinator of the BCKW combinator calculus.
71
49
  """
72
50
 
73
- DELTA: Final = Variable("x").apply_to(
74
- Variable("x")
75
- ).abstract("x")
51
+ DELTA: Final = Variable("x").apply_to(Variable("x")).abstract("x")
76
52
  """
77
53
  Term applying its argument to itself.
78
54
  """
@@ -1,18 +1,10 @@
1
- #!/usr/bin/python3
2
-
3
1
  """Implementations of boolean values and logical operators"""
4
2
 
5
3
  from typing import Final
6
- from . import Variable, Abstraction, Application
7
-
8
- __all__ = (
9
- "TRUE",
10
- "FALSE",
11
- "AND",
12
- "OR",
13
- "NOT",
14
- "IF_THEN_ELSE"
15
- )
4
+
5
+ from . import Abstraction, Application, Variable
6
+
7
+ __all__ = ("AND", "FALSE", "IF_THEN_ELSE", "NOT", "OR", "TRUE")
16
8
 
17
9
  TRUE: Final = Abstraction.curried(("x", "y"), Variable("x"))
18
10
  """
@@ -25,16 +17,14 @@ Term representing False
25
17
  """
26
18
 
27
19
  AND: Final = Abstraction.curried(
28
- ("p", "q"),
29
- Application.with_arguments(Variable("p"), (Variable("q"), Variable("p")))
20
+ ("p", "q"), Application.with_arguments(Variable("p"), (Variable("q"), Variable("p")))
30
21
  )
31
22
  """
32
23
  Term implementing logical conjunction between its two arguments.
33
24
  """
34
25
 
35
26
  OR: Final = Abstraction.curried(
36
- ("p", "q"),
37
- Application.with_arguments(Variable("p"), (Variable("p"), Variable("q")))
27
+ ("p", "q"), Application.with_arguments(Variable("p"), (Variable("p"), Variable("q")))
38
28
  )
39
29
  """
40
30
  Term implementing logical disjunction between its two arguments.
@@ -46,8 +36,7 @@ Term performing logical negation of its argument.
46
36
  """
47
37
 
48
38
  IF_THEN_ELSE: Final = Abstraction.curried(
49
- ("p", "a", "b"),
50
- Application.with_arguments(Variable("p"), (Variable("a"), Variable("b")))
39
+ ("p", "a", "b"), Application.with_arguments(Variable("p"), (Variable("a"), Variable("b")))
51
40
  )
52
41
  """
53
42
  Term evaluating to its second argument if its first argument is TRUE