@@ -2411,20 +2411,99 @@ def integralFunction(self, lower=None, upper=None, datapoints=100):
2411
2411
outputs = [o + " Integral" for o in self .__outputs__ ],
2412
2412
)
2413
2413
2414
+ def isBijective (self ):
2415
+ """Checks whether the Function is bijective. Only applicable to
2416
+ Functions whose source is a list of points, raises an error otherwise.
2417
+
2418
+ Returns
2419
+ -------
2420
+ result : bool
2421
+ True if the Function is bijective, False otherwise.
2422
+ """
2423
+ if isinstance (self .source , np .ndarray ):
2424
+ xDataDistinct = set (self .xArray )
2425
+ yDataDistinct = set (self .yArray )
2426
+ distinctMap = set (zip (xDataDistinct , yDataDistinct ))
2427
+ return len (distinctMap ) == len (xDataDistinct ) == len (yDataDistinct )
2428
+ else :
2429
+ raise TypeError (
2430
+ "Only Functions whose source is a list of points can be "
2431
+ "checked for bijectivity."
2432
+ )
2433
+
2434
+ def isStrictlyBijective (self ):
2435
+ """Checks whether the Function is "strictly" bijective.
2436
+ Only applicable to Functions whose source is a list of points,
2437
+ raises an error otherwise.
2438
+
2439
+ Notes
2440
+ -----
2441
+ By "strictly" bijective, this implementation considers the
2442
+ list-of-points-defined Function bijective between each consecutive pair
2443
+ of points. Therefore, the Function may be flagged as not bijective even
2444
+ if the mapping between the set of points which define the Function is
2445
+ bijective.
2446
+
2447
+ Returns
2448
+ -------
2449
+ result : bool
2450
+ True if the Function is "strictly" bijective, False otherwise.
2451
+
2452
+ Examples
2453
+ --------
2454
+ >>> f = Function([[0, 0], [1, 1], [2, 4]])
2455
+ >>> f.isBijective()
2456
+ True
2457
+ >>> f.isStrictlyBijective()
2458
+ True
2459
+
2460
+ >>> f = Function([[-1, 1], [0, 0], [1, 1], [2, 4]])
2461
+ >>> f.isBijective()
2462
+ False
2463
+ >>> f.isStrictlyBijective()
2464
+ False
2465
+
2466
+ A Function which is not "strictly" bijective, but is bijective, can be
2467
+ constructed as x^2 defined at -1, 0 and 2.
2468
+
2469
+ >>> f = Function([[-1, 1], [0, 0], [2, 4]])
2470
+ >>> f.isBijective()
2471
+ True
2472
+ >>> f.isStrictlyBijective()
2473
+ False
2474
+ """
2475
+ if isinstance (self .source , np .ndarray ):
2476
+ # Assuming domain is sorted, range must also be
2477
+ yData = self .yArray
2478
+ # Both ascending and descending order means Function is bijective
2479
+ yDataDiff = np .diff (yData )
2480
+ return np .all (yDataDiff >= 0 ) or np .all (yDataDiff <= 0 )
2481
+ else :
2482
+ raise TypeError (
2483
+ "Only Functions whose source is a list of points can be "
2484
+ "checked for bijectivity."
2485
+ )
2486
+
2414
2487
def inverseFunction (self , approxFunc = None , tol = 1e-4 ):
2415
2488
"""
2416
2489
Returns the inverse of the Function. The inverse function of F is a
2417
2490
function that undoes the operation of F. The inverse of F exists if
2418
2491
and only if F is bijective. Makes the domain the range and the range
2419
2492
the domain.
2420
2493
2494
+ If the Function is given by a list of points, its bijectivity is
2495
+ checked and an error is raised if it is not bijective.
2496
+ If the Function is given by a function, its bijection is not
2497
+ checked and may lead to innacuracies outside of its bijective region.
2498
+
2421
2499
Parameters
2422
2500
----------
2423
2501
approxFunc : callable, optional
2424
2502
A function that approximates the inverse of the Function. This
2425
2503
function is used to find the starting guesses for the inverse
2426
2504
root finding algorithm. This is better used when the inverse
2427
- in complex but has a simple approximation.
2505
+ in complex but has a simple approximation or when the root
2506
+ finding algorithm performs poorly due to default start point.
2428
2507
The default is None in which case the starting point is zero.
2429
2508
2430
2509
tol : float, optional
@@ -2437,10 +2516,15 @@ def inverseFunction(self, approxFunc=None, tol=1e-4):
2437
2516
A Function whose domain and range have been inverted.
2438
2517
"""
2439
2518
if isinstance (self .source , np .ndarray ):
2440
- # Swap the columns
2441
- source = np .flip (self .source , axis = 1 )
2519
+ if self .isStrictlyBijective ():
2520
+ # Swap the columns
2521
+ source = np .flip (self .source , axis = 1 )
2522
+ else :
2523
+ raise ValueError (
2524
+ "Function is not bijective, so it does not have an inverse."
2525
+ )
2442
2526
else :
2443
- if approxFunc :
2527
+ if approxFunc is not None :
2444
2528
source = lambda x : self .findInput (x , start = approxFunc (x ), tol = tol )
2445
2529
else :
2446
2530
source = lambda x : self .findInput (x , start = 0 , tol = tol )
0 commit comments