physdes-py 0.1__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.
physdes/interval.py ADDED
@@ -0,0 +1,637 @@
1
+ """
2
+ Interval Class
3
+
4
+ This code defines an Interval class, which represents a range of numbers with a lower bound and an upper bound. The purpose of this class is to provide a way to work with intervals of numbers, allowing various operations and comparisons to be performed on them.
5
+
6
+ The Interval class takes two inputs when creating an instance: a lower bound (lb) and an upper bound (ub). These can be either integers or floating-point numbers. The class then stores these values and provides methods to access and manipulate them.
7
+
8
+ The main outputs of this class are the results of various operations on intervals, such as checking if two intervals overlap, finding the intersection between intervals, or calculating the minimum distance between intervals.
9
+
10
+ The class achieves its purpose by implementing a variety of methods that perform calculations and comparisons on the lower and upper bounds of the intervals. For example, the overlaps method checks if two intervals have any numbers in common, while the contains method determines if a given number or interval is entirely within another interval.
11
+
12
+ Some important logic flows in this code include:
13
+
14
+ 1. Comparison operations: The class implements methods like __lt__, __gt__, __le__, and __ge__ to compare intervals with other intervals or single numbers.
15
+ 2. Arithmetic operations: Methods like __add__, __sub__, and __mul__ allow intervals to be added, subtracted, or multiplied by scalar values.
16
+ 3. Set-like operations: The hull_with method finds the smallest interval that contains both the current interval and another interval or number, while intersect_with finds the overlap between two intervals.
17
+
18
+ The code also includes utility functions outside the class, such as hull and enlarge, which can work with both Interval objects and scalar values. These functions provide a more flexible way to perform operations on intervals and numbers.
19
+
20
+ Overall, this Interval class provides a comprehensive set of tools for working with ranges of numbers, which can be useful in various applications such as scheduling, resource allocation, or numerical analysis. It allows programmers to easily manipulate and compare intervals without having to manually handle the lower and upper bounds separately.
21
+ """
22
+
23
+ from typing import Generic, TypeVar, Union
24
+
25
+ from .generic import displacement, min_dist
26
+
27
+ T = TypeVar("T", int, float)
28
+
29
+
30
+ class Interval(Generic[T]):
31
+ __slots__ = ("_lb", "_ub")
32
+
33
+ def __init__(self, lb: T, ub: T) -> None:
34
+ """
35
+ The function initializes an Interval object with lower bound `lb` and upper bound `ub`.
36
+
37
+ :param lb: The `lb` parameter represents the lower bound of the interval. It is of type `T`,
38
+ which means it can be any data type
39
+
40
+ :type lb: T
41
+
42
+ :param ub: The `ub` parameter represents the upper bound of the interval. It is the maximum
43
+ value that the interval can take
44
+
45
+ :type ub: T
46
+
47
+ Examples:
48
+ >>> a = Interval(3, 4)
49
+ >>> print(a)
50
+ [3, 4]
51
+ >>> print(a.lb)
52
+ 3
53
+ >>> print(a.ub)
54
+ 4
55
+ """
56
+ self._lb: T = lb
57
+ self._ub: T = ub
58
+
59
+ def __repr__(self):
60
+ return f"{self.__class__.__name__}({self.lb}, {self.ub}"
61
+
62
+ def __str__(self) -> str:
63
+ """
64
+ The `__str__` function returns a string representation of an Interval object in the format "[lb, ub]".
65
+
66
+ :return: The method `__str__` returns a string representation of the object. In this case, it
67
+ returns a string in the format "[lb, ub]", where lb is the lower bound and ub is the upper bound
68
+ of the interval.
69
+
70
+ Examples:
71
+ >>> a = Interval(3, 4)
72
+ >>> print(a)
73
+ [3, 4]
74
+ """
75
+ return f"[{self.lb}, {self.ub}]"
76
+
77
+ @property
78
+ def lb(self) -> T:
79
+ """
80
+ The function `lb` returns the lower bound of an interval.
81
+
82
+ :return: The method is returning the lower bound of the interval.
83
+
84
+ Examples:
85
+ >>> a = Interval(3, 4)
86
+ >>> a.lb
87
+ 3
88
+ """
89
+ return self._lb
90
+
91
+ @property
92
+ def ub(self) -> T:
93
+ """
94
+ The function `ub` returns the upper bound of an interval.
95
+
96
+ :return: The method is returning the upper bound of the interval.
97
+
98
+ Examples:
99
+ >>> a = Interval(3, 4)
100
+ >>> a.ub
101
+ 4
102
+ """
103
+ return self._ub
104
+
105
+ def is_invalid(self) -> bool:
106
+ return self.lb > self.ub
107
+
108
+ # def copy(self) -> "Interval[T]":
109
+ # """
110
+ # The `copy` function returns a new instance of the same class with the same lower and upper
111
+ # bounds.
112
+ # :return: The `copy` method is returning a new instance of the same class as `self`, with the
113
+ # same lower bound (`_lb`) and upper bound (`_ub`) values.
114
+ #
115
+ # Examples:
116
+ # >>> a = Interval(3, 4)
117
+ # >>> print(a.copy())
118
+ # [3, 4]
119
+ # """
120
+ # S = type(self)
121
+ # return S(self._lb, self._ub)
122
+
123
+ def length(self) -> T:
124
+ """
125
+ The function returns the length of a range defined by the upper bound (ub) and lower bound (lb)
126
+ attributes.
127
+
128
+ :return: The length of the object, which is calculated by subtracting the upper bound (ub) from
129
+ the lower bound (lb).
130
+
131
+ Examples:
132
+ >>> a = Interval(3, 4)
133
+ >>> a.length()
134
+ 1
135
+ """
136
+ return self.ub - self.lb
137
+
138
+ def __eq__(self, other) -> bool:
139
+ """
140
+ The function checks if two Interval objects have the same lower and upper bounds.
141
+
142
+ :param other: The "other" parameter represents another object that we are comparing with the
143
+ current object. In this case, it is used to compare two Interval objects and check if they are
144
+ equal
145
+
146
+ :return: The `__eq__` method is returning a boolean value.
147
+
148
+ Examples:
149
+ >>> a = Interval(3, 4)
150
+ >>> b = Interval(3, 5)
151
+ >>> a == b
152
+ False
153
+ """
154
+ return (self.lb, self.ub) == (other.lb, other.ub)
155
+
156
+ def __lt__(self, other) -> bool:
157
+ """
158
+ The function compares the upper bound of the current object with the other object and returns
159
+ True if the upper bound of the current object is less than the other object.
160
+
161
+ :param other: The "other" parameter represents the value that the current object is being
162
+ compared to. In this case, it is being compared to the upper bound (ub) of the current object
163
+
164
+ :return: The code is returning a boolean value indicating whether the upper bound of the current
165
+ interval object is less than the other object.
166
+
167
+ Examples:
168
+ >>> a = Interval(3, 4)
169
+ >>> b = Interval(3, 5)
170
+ >>> a < b
171
+ False
172
+ >>> b < a
173
+ False
174
+ """
175
+ return self.ub < other
176
+
177
+ def __gt__(self, other) -> bool:
178
+ """
179
+ The function compares the upper bound of the current object with the other object and returns
180
+ True if the lower bound of the current object is greater than the other object.
181
+
182
+ :param other: The "other" parameter represents the value that the current object is being
183
+ compared to. In this case, it is being compared to the lower bound (lb) of the current object
184
+
185
+ :return: The code is returning a boolean value indicating whether the lower bound of the current
186
+ interval object is greater than the other object.
187
+
188
+ Examples:
189
+ >>> a = Interval(3, 4)
190
+ >>> b = Interval(3, 5)
191
+ >>> a > b
192
+ False
193
+ >>> b > a
194
+ False
195
+ """
196
+ return self.lb > other
197
+
198
+ def __le__(self, other) -> bool:
199
+ """
200
+ The function returns True if the current interval is less than or equal to the the other interval.
201
+
202
+ :param other: The `other` parameter represents another instance of the `Interval` class that we
203
+ are comparing to the current instance
204
+
205
+ :return: The code is returning a boolean value.
206
+
207
+ Examples:
208
+ >>> a = Interval(3, 4)
209
+ >>> b = Interval(3, 5)
210
+ >>> a <= b
211
+ True
212
+ >>> b <= a
213
+ True
214
+ """
215
+ return not (other < self.lb)
216
+
217
+ def __ge__(self, other) -> bool:
218
+ """
219
+ The function returns True if the current interval is greater than or equal to the the other interval.
220
+
221
+ :param other: The `other` parameter represents another instance of the `Interval` class that we
222
+ are comparing to the current instance
223
+
224
+ :return: The code is returning a boolean value.
225
+
226
+ Examples:
227
+ >>> a = Interval(3, 4)
228
+ >>> b = Interval(3, 5)
229
+ >>> a >= b
230
+ True
231
+ >>> b >= a
232
+ True
233
+ """
234
+ return not (self.ub < other)
235
+
236
+ def __neg__(self) -> "Interval[T]":
237
+ """
238
+ The `__neg__` function returns a new instance of the class with the lower and upper bounds negated.
239
+
240
+ :return: The `__neg__` method returns a new instance of the same class (`S`) with the lower
241
+ bound (`lb`) and upper bound (`ub`) negated.
242
+
243
+ Examples:
244
+ >>> a = Interval(3, 4)
245
+ >>> print(-a)
246
+ [-4, -3]
247
+ """
248
+ S = type(self)
249
+ return S(-self.ub, -self.lb)
250
+
251
+ def __iadd__(self, rhs: T) -> "Interval[T]":
252
+ """
253
+ The `__iadd__` method allows for in-place addition of an `Interval` object.
254
+
255
+ :param rhs: The parameter `rhs` represents the right-hand side value that is being added to the
256
+ current object. In this case, it is expected to be of type `T`, which is a generic type
257
+
258
+ :type rhs: T
259
+
260
+ :return: The method `__iadd__` returns `self`, which is an instance of the class `"Interval[T]"`.
261
+
262
+ Examples:
263
+ >>> a = Interval(3, 4)
264
+ >>> a += 10
265
+ >>> print(a)
266
+ [13, 14]
267
+ """
268
+ self._lb += rhs
269
+ self._ub += rhs
270
+ return self
271
+
272
+ def __add__(self, rhs: T) -> "Interval[T]":
273
+ """
274
+ The function overloads the "+" operator to add a constant value to the lower and upper bounds of
275
+ an Interval object.
276
+
277
+ :param rhs: The parameter `rhs` stands for "right-hand side" and represents the value that is
278
+ being added to the current object
279
+
280
+ :type rhs: T
281
+
282
+ :return: The method is returning a new instance of the class `S` (which is the same type as
283
+ `self`) with the lower bound (`lb`) and upper bound (`ub`) incremented by `rhs`.
284
+
285
+ Examples:
286
+ >>> a = Interval(3, 4)
287
+ >>> print(a + 10)
288
+ [13, 14]
289
+ """
290
+ S = type(self)
291
+ return S(self.lb + rhs, self.ub + rhs)
292
+
293
+ def __isub__(self, rhs: T) -> "Interval[T]":
294
+ """
295
+ The function subtracts a value from both the lower and upper bounds of an Interval object and
296
+ returns the modified object.
297
+
298
+ :param rhs: The parameter `rhs` represents the right-hand side value that will be subtracted
299
+ from the current object. In this case, it is expected to be of type `T`, which is a generic type
300
+
301
+ :type rhs: T
302
+
303
+ :return: The method is returning `self`, which is an instance of the class that the method
304
+ belongs to.
305
+
306
+ Examples:
307
+ >>> a = Interval(3, 4)
308
+ >>> a -= 1
309
+ >>> print(a)
310
+ [2, 3]
311
+ """
312
+ self._lb -= rhs
313
+ self._ub -= rhs
314
+ return self
315
+
316
+ def __sub__(self, rhs: T) -> "Interval[T]":
317
+ """
318
+ The function subtracts a value from the lower and upper bounds of an interval and returns a new
319
+ interval.
320
+
321
+ :param rhs: The parameter `rhs` stands for "right-hand side" and represents the value that is
322
+ being subtracted from the interval
323
+
324
+ :type rhs: T
325
+
326
+ :return: The method is returning a new instance of the class `S` (which is the same type as
327
+ `self`) with the lower bound (`lb`) and upper bound (`ub`) subtracted by `rhs`.
328
+
329
+ Examples:
330
+ >>> a = Interval(3, 4)
331
+ >>> print(a - 1)
332
+ [2, 3]
333
+ """
334
+ S = type(self)
335
+ return S(self.lb - rhs, self.ub - rhs)
336
+
337
+ def __imul__(self, rhs: T) -> "Interval[T]":
338
+ """
339
+ The `__imul__` method allows for in-place multiplication of an `Interval` object.
340
+
341
+ :param rhs: The parameter `rhs` represents the right-hand side value that is being multiplied to the
342
+ current object. In this case, it is expected to be of type `T`, which is a generic type
343
+
344
+ :type rhs: T
345
+
346
+ :return: The method `__imul__` returns `self`, which is an instance of the class `"Interval[T]"`.
347
+
348
+ Examples:
349
+ >>> a = Interval(3, 4)
350
+ >>> a *= 10
351
+ >>> print(a)
352
+ [30, 40]
353
+ """
354
+ self._lb *= rhs
355
+ self._ub *= rhs
356
+ return self
357
+
358
+ def __mul__(self, rhs: T) -> "Interval[T]":
359
+ """
360
+ The function overloads the "*" operator to multiply a constant value to the lower and upper bounds of
361
+ an Interval object.
362
+
363
+ :param rhs: The parameter `rhs` stands for "right-hand side" and represents the value that is
364
+ being multiplied to the current object
365
+
366
+ :type rhs: T
367
+
368
+ :return: The method is returning a new instance of the class `S` (which is the same type as
369
+ `self`) with the lower bound (`lb`) and upper bound (`ub`) incremented by `rhs`.
370
+
371
+ Examples:
372
+ >>> a = Interval(3, 4)
373
+ >>> print(a * 10)
374
+ [30, 40]
375
+ """
376
+ S = type(self)
377
+ return S(self.lb * rhs, self.ub * rhs)
378
+
379
+ def overlaps(self, other: Union["Interval[T]", T]) -> bool:
380
+ """
381
+ The `overlaps` function checks if two intervals overlap with each other.
382
+
383
+ :param other: The parameter "other" is of type Union["Interval[T]", T], which means it can accept either
384
+ an object of the same class as "self" or an object of type "T"
385
+
386
+ :type other: Union["Interval[T]", T]
387
+
388
+ :return: a boolean value, either True or False.
389
+
390
+ Examples:
391
+ >>> a = Interval(3, 5)
392
+ >>> a.overlaps(Interval(4, 9))
393
+ True
394
+ >>> a.overlaps(Interval(6, 9))
395
+ False
396
+ """
397
+ return not (self < other or other < self)
398
+
399
+ def contains(self, obj: Union["Interval[T]", T]) -> bool:
400
+ """
401
+ The `contains` function checks if an object is contained within a given interval.
402
+
403
+ :param obj: The `obj` parameter can be either an instance of the `Interval` class or an integer
404
+ :type obj: Union["Interval[T]", T]
405
+ :return: The `contains` method returns a boolean value indicating whether the given object is
406
+ contained within the interval.
407
+
408
+ Examples:
409
+ >>> a = Interval(3, 8)
410
+ >>> a.contains(4)
411
+ True
412
+ >>> a.contains(Interval(4, 7))
413
+ True
414
+ >>> a.contains(Interval(6, 9))
415
+ False
416
+ """
417
+ # `obj` can be an Interval or int
418
+ if isinstance(obj, Interval):
419
+ return self.lb <= obj.lb and obj.ub <= self.ub
420
+ else: # assume scalar
421
+ return self.lb <= obj <= self.ub
422
+
423
+ def hull_with(self, obj: Union["Interval[T]", T]):
424
+ """
425
+ The `hull_with` function takes an object (either an `Interval` or a scalar) and returns a new
426
+ `Interval` object that represents the hull (smallest interval that contains both intervals) of
427
+ the current `Interval` object and the input object.
428
+
429
+ :param obj: The `obj` parameter can be either an instance of the same class (`"Interval[T]"`) or a scalar value (`T`)
430
+ :type obj: Union["Interval[T]", T]
431
+ :return: The method `hull_with` returns an `Interval` object.
432
+
433
+ Examples:
434
+ >>> a = Interval(3, 8)
435
+ >>> print(a.hull_with(Interval(4, 7)))
436
+ [3, 8]
437
+ >>> print(a.hull_with(Interval(6, 9)))
438
+ [3, 9]
439
+ """
440
+ if isinstance(obj, Interval):
441
+ return Interval(min(self.lb, obj.lb), max(self.ub, obj.ub))
442
+ else: # assume scalar
443
+ return Interval(min(self.lb, obj), max(self.ub, obj))
444
+
445
+ def intersect_with(self, obj: Union["Interval[T]", T]):
446
+ """
447
+ The `intersect_with` function takes in an object and returns the intersection between the
448
+ object and the current interval.
449
+
450
+ :param obj: The `obj` parameter can be either an instance of the `"Interval[T]"` class (which is the same
451
+ class as `self`), or it can be of type `T`, which is a generic type
452
+
453
+ :type obj: Union["Interval[T]", T]
454
+
455
+ :return: The `intersect_with` method returns an `Interval` object that represents the
456
+ intersection between the current `Interval` object (`self`) and the input object (`obj`).
457
+
458
+ Examples:
459
+ >>> a = Interval(3, 8)
460
+ >>> print(a.intersect_with(4))
461
+ [4, 4]
462
+ >>> print(a.intersect_with(Interval(4, 7)))
463
+ [4, 7]
464
+ >>> print(a.intersect_with(Interval(6, 9)))
465
+ [6, 8]
466
+ >>> print(a.intersect_with(Interval(3, 5)))
467
+ [3, 5]
468
+ >>> print(a.intersect_with(Interval(5, 7)))
469
+ [5, 7]
470
+ >>> print(a.intersect_with(Interval(3, 6)))
471
+ [3, 6]
472
+ >>> print(a.intersect_with(Interval(5, 8)))
473
+ [5, 8]
474
+ >>> print(a.intersect_with(Interval(3, 7)))
475
+ [3, 7]
476
+ """
477
+ # `a` can be an Interval or int
478
+ # assert self.overlaps(obj)
479
+ if isinstance(obj, Interval):
480
+ return Interval(max(self.lb, obj.lb), min(self.ub, obj.ub))
481
+ else: # assume scalar
482
+ return Interval(max(self.lb, obj), min(self.ub, obj))
483
+
484
+ def min_dist_with(self, obj: Union["Interval[T]", T]):
485
+ """
486
+ The function calculates the minimum distance between two objects.
487
+
488
+ :param obj: The parameter `obj` can be of type `"Interval[T]"` or `T`
489
+ :type obj: Union["Interval[T]", T]
490
+ :return: The function `min_dist_with` returns the minimum distance between the given object
491
+ `obj` and the current object `self`.
492
+
493
+ Examples:
494
+ >>> a = Interval(3, 5)
495
+ >>> print(a.min_dist_with(2))
496
+ 1
497
+ >>> print(a.min_dist_with(Interval(4, 7)))
498
+ 0
499
+ >>> print(a.min_dist_with(Interval(6, 9)))
500
+ 1
501
+ >>> print(a.min_dist_with(Interval(3, 5)))
502
+ 0
503
+ >>> print(a.min_dist_with(Interval(5, 7)))
504
+ 0
505
+ """
506
+ if self < obj:
507
+ return min_dist(self.ub, obj)
508
+ if obj < self:
509
+ return min_dist(self.lb, obj)
510
+ return 0
511
+
512
+ def displace(self, obj: "Interval[T]"):
513
+ """
514
+ The `displace` function takes an object as an argument and returns a new Interval object with
515
+ the lower and upper bounds displaced by the corresponding bounds of the input object.
516
+
517
+ :param obj: The `obj` parameter is an object of the same class as the `self` object. It
518
+ represents another interval that will be used to displace the current interval
519
+
520
+ :type obj: "Interval[T]"
521
+
522
+ :return: The `displace` method returns an `Interval` object.
523
+
524
+ Examples:
525
+ >>> a = Interval(3, 5)
526
+ >>> print(a.displace(Interval(4, 7)))
527
+ [-1, -2]
528
+ >>> print(a.displace(Interval(6, 9)))
529
+ [-3, -4]
530
+ """
531
+ lb = displacement(self.lb, obj.lb)
532
+ ub = displacement(self.ub, obj.ub)
533
+ return Interval(lb, ub)
534
+
535
+ # def min_dist_change_with(self, obj: Union["Interval[T]", T]):
536
+ # """[summary]
537
+ #
538
+ # Args:
539
+ # other ([type]): [description]
540
+ #
541
+ # Returns:
542
+ # [type]: [description]
543
+ # """
544
+ # if self < obj:
545
+ # self._lb = self._ub
546
+ # return min_dist_change(self._ub, obj)
547
+ # if obj < self:
548
+ # self._ub = self._lb
549
+ # return min_dist_change(self._lb, obj)
550
+ # S = type(self)
551
+ # if isinstance(obj, S):
552
+ # self = obj = self.intersect_with(obj) # what???
553
+ # else: # assume scalar
554
+ # self._ub = self._lb = obj
555
+ # return 0
556
+
557
+ def enlarge_with(self, alpha: T) -> "Interval[T]":
558
+ """
559
+ The `enlarge_with` function takes a value `alpha` and returns a new instance of the same type
560
+ with the lower bound decreased by `alpha` and the upper bound increased by `alpha`.
561
+
562
+ :param alpha: The parameter "alpha" represents the amount by which the interval should be enlarged
563
+
564
+ :type alpha: T
565
+
566
+ :return: The method `enlarge_with` returns a new instance of the same class (`"Interval[T]"`) with the
567
+ lower bound decreased by `alpha` and the upper bound increased by `alpha`.
568
+
569
+ Examples:
570
+ >>> a = Interval(3, 5)
571
+ >>> print(a.enlarge_with(2))
572
+ [1, 7]
573
+ """
574
+ S = type(self)
575
+ return S(self._lb - alpha, self._ub + alpha)
576
+
577
+
578
+ def hull(lhs, rhs):
579
+ """
580
+ The `hull` function calculates the convex hull of two objects.
581
+
582
+ :param lhs: The `lhs` parameter represents the left-hand side of the operation, while the `rhs`
583
+ parameter represents the right-hand side of the operation
584
+
585
+ :param rhs: The `rhs` parameter is the right-hand side of the operation. It can be any value or
586
+ object that supports the `hull_with` method
587
+
588
+ :return: the hull of the input arguments.
589
+
590
+ Examples:
591
+ >>> a = Interval(3, 5)
592
+ >>> print(hull(a, 4))
593
+ [3, 5]
594
+ >>> print(hull(a, Interval(4, 7)))
595
+ [3, 7]
596
+ >>> print(hull(a, Interval(6, 9)))
597
+ [3, 9]
598
+ """
599
+ if hasattr(lhs, "hull_with"):
600
+ return lhs.hull_with(rhs)
601
+ elif hasattr(rhs, "hull_with"):
602
+ return rhs.hull_with(lhs)
603
+ else: # assume scalar
604
+ return Interval(lhs, rhs) if lhs < rhs else Interval(rhs, lhs)
605
+
606
+
607
+ def enlarge(lhs, rhs):
608
+ """
609
+ The `enlarge` function takes two arguments, `lhs` and `rhs`, and returns the result of enlarging
610
+ `lhs` by `rhs`.
611
+
612
+ :param lhs: The `lhs` parameter represents the left-hand side of the operation. It can be either an
613
+ object that has a method `enlarge_with`, or a scalar value
614
+
615
+ :param rhs: The parameter `rhs` is the value by which the `lhs` object will be enlarged
616
+
617
+ :type rhs: T
618
+
619
+ :return: an enlarged interval or scalar value.
620
+
621
+ Examples:
622
+ >>> a = Interval(3, 5)
623
+ >>> print(enlarge(a, 2))
624
+ [1, 7]
625
+ >>> print(enlarge(a, -1))
626
+ [4, 4]
627
+ """
628
+ if hasattr(lhs, "enlarge_with"):
629
+ return lhs.enlarge_with(rhs)
630
+ else: # assume scalar
631
+ return Interval(lhs - rhs, lhs + rhs)
632
+
633
+
634
+ if __name__ == "__main__":
635
+ import doctest
636
+
637
+ doctest.testmod()