Source code for geotecha.piecewise.piecewise_linear_1d

# geotecha - A software suite for geotechncial engineering
# Copyright (C) 2018  Rohan T. Walker (rtrwalker@gmail.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see http://www.gnu.org/licenses/gpl.html.

"""
One dimensional piecwise linear relationships and manipulations

"""
from __future__ import print_function, division

import numpy as np
import math
import sys
import matplotlib.pyplot as plt
import copy
import operator


[docs]def has_steps(x): """Check if data points have any step changes True if any two consecutive x values are equal. Parameters ---------- x : array_like x-coordinates Returns ------- out : boolean Returns True if any two consecutive x values are equal. """ #TODO: maybe check for bad segments such as x=[2,2] y=[10,10] x = np.asarray(x) return np.any(np.diff(x)==0)
[docs]def is_initially_increasing(x): """Are first two values increasing? Finds 1st instance where x[i+1] != x[i] and checks if x[i+1] > x[i]. Parameters ---------- x : array_like 1 dimensional data to check Returns ------- out : ``int`` Returns True if 2nd value is greater than the 1st value. Returns False if 2nd value is less than the 1st value. """ # This might be slow for long lists, perhaps just loop through # until x[i+1]!=x[i] if x[1] != x[0]: return x[1] > x[0] x = np.asarray(x) return np.where(np.diff(x) != 0)[0][0] > 0
#used info from http://stackoverflow.com/questions/4983258/python-how-to-check-list-monotonicity
[docs]def strictly_increasing(x): """Checks all x[i+1] > x[i] Parameters ---------- x : 1d array Series of x values Returns ------- out : True/False True if each x value is greater than the preceeding value. See Also -------- strictly_decreasing : Check for decreasing values, no equality allowed. non_increasing : Less stringent decreasing check, allows equal values. non_decreasing : Less stringent increasing check, allows equal values. """ x = np.asarray(x) return np.all(np.diff(x)>0)
[docs]def strictly_decreasing(x): """Checks all x[i+1] < x[i] Parameters ---------- x : 1d array Series of x values Returns ------- out : True/False True if each x value is less than the preceeding value. See Also -------- strictly_increasing : Check for increasing values, no equality allowed. non_increasing : Less stringent decreasing check, allows equal values. non_decreasing : Less stringent increasing check, allows equal values. """ x = np.asarray(x) return np.all(np.diff(x)<0)
[docs]def non_increasing(x): """Checks all x[i+1] <= x[i] Parameters ---------- x : 1d array Series of x values Returns ------- out : True/False True if each x value is less than or equal to the preceeding value. See Also -------- strictly_increasing : Check for increasing values, no equality allowed. strictly_decreasing : Check for decreasing values, no equality allowed. non_decreasing : Less stringent increasing check, allows equal values. """ x = np.asarray(x) return np.all(np.diff(x)<=0)
[docs]def non_decreasing(x): """Checks all x[i+1] >= x[i] Parameters ---------- x : 1d array Series of x values Returns ------- out : True/False True if each x value is greater than or equal to the preceeding value. See Also -------- strictly_increasing : Check for increasing values, no equality allowed strictly_decreasing : Check for decreasing values, no equality allowed non_increasing : Less stringent decreasing check, allows equal values """ x = np.asarray(x) return np.all(np.diff(x)>=0)
[docs]def non_increasing_and_non_decreasing_parts(x, include_end_point=False): """Indexes of each non-increasing and non-decreasing section of a list Parameters ---------- x : list or 1d array List of values. include_end_point : True/False, optional If True then the index of the last point in a non-increasing or non-decreasing run is included. Default include_end_point=False, i.e. only the start index of each line segment is given. Returns ------- A : list of list Each element of A is a list of the start indices of each line segment that is part of a particular non-increasing or non-decreasing run. Notes ----- This funciton only returns start indices for each line segment. Lets say x is [0, 4 , 5.5] then A will be [[0,1]]. If you do x[A[0]] then you will get [0, 4] i.e. no end point. To get the whole increasing or decreasing portion including the end point you need to do something like x[A[0].append[A[0][-1]+1]]. """ #TODO: maybe return slice object rather than a list of all the indexes as they will be contiguous anyway x = np.asarray(x) sign_changes = np.sign(np.diff(x)) A = [[0]] current_sign = sign_changes[np.where(sign_changes != 0)[0][0]] for i, sgn in enumerate(sign_changes.tolist()): if i == 0: continue if sgn != 0 and sgn != current_sign: if include_end_point: A[-1].append(i) A.append([]) current_sign = sgn A[-1].append(i) if include_end_point: #append final end point A[-1].append(A[-1][-1] + 1) return A
[docs]def force_strictly_increasing(x, y = None, keep_end_points = True, eps = 1e-15): """Force a non-decreasing or non-increasing list to be strictly increasing Adds or subtracts tiny amounts (multiples of `eps`) from the x values in step changes to ensure no two consecutive x values are equal (i.e. make x strictly increasing). The adjustments are small enough that for all intents and purposes the data behaves as before; it can now however be easily used in straightforward interpolation functions that require strictly increasing x data. Any data where x is non-increasing will be reversed. Parameters ---------- x : array_like List of x coordinates. y : array_like, optional List of y coordinates, (Default y=None, if known that x is non-decreasing then y will not be affected). keep_end_points : ``boolean``, optional Determines which x value of the step change is adjusted. Consider x=[1,1] and y=[20,40]. If keep_end_points==True then new data will be x=[0.9999,1], y=[20,40]. If keep_end_points==False then data will be x=[1, 1.0001], y=[20,40] eps : float, optional Amount to add/subtract from x (default is 1e-15). To ensure consecutive step changes are handled correctly multipes of `eps` will be added and subtracted. e.g. if there are a total of five steps in the data then the first step would get 5*`eps` adjustment, the second step 4*`eps` adjustment and so on. """ x = np.asarray(x) y = np.asarray(y) if strictly_increasing(x): return x, y if strictly_decreasing(x): return x[::-1], y[::-1] if non_increasing(x): x = x[::-1] y = y[::-1] if not non_decreasing(x): raise ValueError("x data is neither non-increasing, nor non-decreasing, therefore cannot force to strictly increasing") steps = np.where(np.diff(x) == 0)[0] if keep_end_points: f = -1 * eps d = 0 dx = np.arange(len(steps), 0, -1) * f else: f = 1 * eps d = 1 dx = np.arange(1, len(steps) + 1) * f x[steps + d] = x[steps + d] + dx return x, y
[docs]def force_non_decreasing(x, y = None): """Force non-increasing x, y data to non_decreasing by reversing the data Leaves already non-decreasing data alone. Parameters ---------- x, y: array_like x and y coordinates Returns ------- x, y : 1d ndarray, 1d ndarray Non-decreasing version of x, y """ x = np.asarray(x) y = np.asarray(y) if non_decreasing(x): return x, y if not non_increasing(x): raise ValueError("x data is neither non-increasing, nor non-decreasing, therefore cannot force to non-decreasing") return x[::-1], y[::-1]
[docs]def start_index_of_ramps(x, y): """Find the start indices of the ramp line segments in x, y data. An example of a 'ramp' x=[0,2], y=[10,15]. i.e. not a vertical line and not a horizontal line. Assumes data is non_decreasing. Parameters ---------- x, y : array_like x and y coords (must be non-decreasing). Returns ------- out : 1d ndarray Start indices of all ramps. """ x = np.asarray(x) y = np.asarray(y) return np.where((np.diff(x) != 0) & (np.diff(y) != 0))[0]
[docs]def start_index_of_constants(x, y): """Find the start indices of the constant line-segments in x, y data. An example of a 'constant' x=[0,2], y=[15,15]. i.e. a horizontal line Assumes data is non_decreasing. Segments such as x=[1,1], y=[2,2] are ignored. Parameters ---------- x, y : array_like x and y coords (must be non-decreasing). Returns ------- out : 1d ndarray Start indices of all constant segments. """ x = np.asarray(x) y = np.asarray(y) return np.delete(np.where(np.diff(y)==0)[0], np.where((np.diff(x)==0) & (np.diff(y)==0))[0])
[docs]def start_index_of_steps(x, y): """Find the start indices of the step line-segments in x, y data. An example of a 'step' x=[0, 0], y=[10, 15]. i.e. a vertical line. Assumes data is non_decreasing. Segments such as x=[1, 1], y=[2, 2] are ignored. Parameters ---------- x, y : array_like x and y coords (must be non-decreasing) Returns ------- out : 1d ndarray Start indices of all step segments. """ x = np.asarray(x) y = np.asarray(y) return np.delete(np.where(np.diff(x)==0)[0], np.where((np.diff(x)==0) & (np.diff(y)==0))[0])
[docs]def segment_containing_xi(x, xi, subset=None, choose_max=False): """Find start index of line segment in which xi falls Find index where x[i] <= xi <= x[i+1] ignoring steps. `choose_max` determines what happens when more than one segment satisfies the condition e.g. the boundary between two segments; taking either the maximum index (`choose_max` True), or the minimum index (`choose_max`= False). Parameters ---------- x : array_like, float x coordinates. xi : array_like, float Values to place in segments. subset : array_like, optional Restrict search to segments starting with indices in `subset`. Default subset=None i.e. search all segments. choose_max : boolean, optional When False (default), the minumum index that satisfies the condition is returned. When True the maximum index that satisfies the condition is returned. Default choose_max=False. See Notes below Returns ------- A : list of single element lists Each sub-list is the start index of the segment that contains xi. Returning each value in a list allows "for i in A[0]" type constructs which for an empty list will do nothing. Notes ----- If x data has switch backs (i.e. not non-decreasing and not non-increasing) then if `choose_max` = True then the index returned will be in the last group of segments. This can be useful as when determining when something ends. e.g. say x are strictly incresing time values and y zig zags up and down for a time then decays to zero. A regular interpolation will give you any y value at a given x value. But you might want to answer a question such as 'at what time does y finally fall below 10'. You want to search only the last decaying section. By plugging the y values in as `x` and using `choose_max` = True, you will get the segment where this happens. This function is somewhat similar to the `numpy.digitize` function that places x values into bins. `segment_containing_xi` however, does not insist on monotonic data and won't break down if steps are included; it is also possible to put both bounds in the same segement. Say for x = [0,1,2,3] and xi1 = 1 and xi2 = 2. If `choose_max` is False for both points then xi1 will be in segment 0, and xi2 in segment 1. But if you use `choose_max` = True for xi1 then it be in segment 1 (the same as xi2). """ x = np.asarray(x) xi = np.atleast_1d(xi) # if isinstance(xi, numbers.Number): # xi = np.array([xi]) # xi = np.asarray(xi) if subset is None: subset = np.arange(len(x)-1) if len(subset)==0: #subset isempty return [np.array([],dtype = int) for v in xi] subset = np.asarray(subset) #this commented one doesn't work for descending #A = [subset[(v>=x[subset]) & (v<=x[subset+1]) & (x[subset]!=x[subset+1])] for v in xi] A = [subset[(abs(v-x[subset])<=abs(x[subset]-x[subset+1])) & (abs(v-x[subset+1])<=abs(x[subset]-x[subset+1])) & (x[subset]!=x[subset+1])] for v in xi] if choose_max: f = -1 else: f = 0 for i, v in enumerate(A): if v.size>0: A[i] = [v[f]] else: A[i] = [] return A
[docs]def segments_less_than_xi(x, xi, subset=None, or_equal_to=False): """Find start index of line segments that end before xi Finds all segments where end point of segment is less than xi. Assumes non-decreasing `x` data. Parameters ---------- x : array_like, float x coordinates. xi : array_like, float Values to check if segments start after. subset : array_like, optional Restrict search to segments starting with indices in `subset`. Default subset=None, which searches all segments. or_equal_to : ``boolean``, optional If False (default) then conditon is formulated with '<'. If True then condition is formulated with '<='. Generally only used to include/exclude step loads. Returns ------- out : list of 1d numpy.ndarray List contains len(xi) 1d numpy.ndarray corresponding to xi. """ x = np.asarray(x) xi = np.atleast_1d(xi) if subset is None: subset = np.arange(len(x)-1) subset = np.asarray(subset) if or_equal_to: return [subset[x[subset+1] <= v] for v in xi] else: return [subset[x[subset+1] < v] for v in xi]
[docs]def ramps_constants_steps(x, y): """Find the ramp, constant, and step line segments in x, y data Returns ------- ramps : 1d ndarray `start_index_of_ramps` constants : 1d ndarray `start_index_of_constants` steps : 1d ndarray `start_index_of_constants` See Also -------- start_index_of_ramps : Find ramps. start_index_of_steps : Find steps. start_index_of_constants : Find constants. """ x = np.asarray(x) y = np.asarray(y) ramps = start_index_of_ramps(x,y) constants = start_index_of_constants(x,y) steps = start_index_of_steps(x,y) return (ramps, constants, steps)
[docs]def segment_containing_also_segments_less_than_xi(x, y, xi, steps_or_equal_to=True, ramp_const_or_equal_to=False, choose_max=False): """Determine ramp, constant and step segments containing xi as well as segments less than xi. Function does minimal calculations itself, essentially calling other functions and returning a tuple. Useful for piecewise linear loading functions when you segements after a certain time are irrelevant. Parameters ---------- x, y : array_like x and y coords (must be non-decreasing). xi : array_like, float Values to check check segments against. steps_or_equal_to : ``boolean``, optional If True (default) then any step segment that xi falls on/in will be included in the steps 'less than' list. ramp_const_or_equal_to : ``boolean``, optional If False (default) then any ramp or constant segment that xi falls on the start will not be included in the ramps and constants 'less than' list. choose_max : ``boolean``, optional If False (default), then the minimum segment of multiple ramp segments that contain xi falls will be included in the 'contains' lists. If True then the maximum segment will be included. Returns ------- ramps_less_than_xi : ndarray `segments_less_than_xi` for ramps. constants_less_than_xi : ndarray `segments_less_than_xi` for constants. steps_less_than_xi : ndarray `segments_less_than_xi` for steps. ramps_containing_xi : ndarray Start index of ramp segment containing xi. constants_containing_xi : ndarray Start index of constant segment containing xi. See Also -------- ramps_constants_steps : find ramp, constant, and step segments segments_less_than_xi : find segments after xi segment_containing_xi : find segments that contain xi """ x = np.asarray(x) y = np.asarray(y) xi = np.atleast_1d(xi) ramps, constants, steps = ramps_constants_steps(x,y) ramps_less_than_xi = segments_less_than_xi(x, xi, subset=ramps, or_equal_to=ramp_const_or_equal_to) constants_less_than_xi = segments_less_than_xi(x, xi, subset=constants, or_equal_to=ramp_const_or_equal_to) steps_less_than_xi = segments_less_than_xi(x, xi, subset=steps, or_equal_to=steps_or_equal_to) # the temptation to call segment_containing_xi here with subset=ramps and then subset=constants might lead to xi being in both a ramp or a constant contains = segment_containing_xi(x, xi, choose_max=choose_max) ramps_containing_xi = [np.array([],dtype=int)] * len(xi) constants_containing_xi = [np.array([],dtype=int)] * len(xi) for i, arr in enumerate(contains): #decide when xi falls on edge of segment wether it is in a ramp or a constant segment for v in arr: #this will skip empty arrays if v in ramps: ramps_containing_xi[i] = np.array([v], dtype = int) else: constants_containing_xi[i] = np.array([v], dtype = int) return (ramps_less_than_xi, constants_less_than_xi, steps_less_than_xi, ramps_containing_xi, constants_containing_xi)
[docs]def segment_containing_xi_also_containing_xj(x, xi, xj, subset=None): """Find start index of segments that xi and xj fall in trying to have them in the same section. Find highest i where x[i] <= xi <= x[i+1] ignoring steps. Find lowest j where x[j] <= xj <= x[j+1] ignoring steps. This will minimise the number of segments between xi and xj. Usually xj should be greater than xi. Function does no calculations itself, rather calling segment_containing_xi twice and returning a tuple. Parameters ---------- x : array_like, float x coordinates. xi, xj : array_like, float x values to from which to determin containing segment. subset : array_like, optional Restrict search to segments starting with indices in `subset`. Default subset=None which searches all segments. Returns ------- seg_xi, seg_xj : list of single element lists Each sub-list is the start index of the segement that contains xi or xj. See Also -------- segments_containing_xi : Function called for `xi` and `xj`. """ #commented code may provide minimal speed up # x = np.asarray(x) # # if isinstance(xi, numbers.Number): # xi = np.array([xi]) # xi = np.asarray(xi) # # if isinstance(xj, numbers.Number): # xj = np.array([xj]) # xj = np.asarray(xj) # # # # if subset is None: # subset = np.arange(len(x)-1) # if len(subset)==0: #subset isempty # return ([np.array([],dtype = int) for v in xi], [np.array([],dtype = int) for v in xi]) # # subset = np.asarray(subset) return (segment_containing_xi(x, xi, subset, choose_max=True), segment_containing_xi(x, xj, subset, choose_max = False))
[docs]def segments_between_xi_and_xj(x, xi, xj): """Find line segments that exclusively contain both, only one of, and in between xi and xj. Determine if xi and xj are both in the same segment. When xi and xj are in different segments find the segment that contains xi and the segment that contains xj and any segments in between them. Results are only obvious when x is strictly increasing and xi<xj. This is useful when integrating between two points; the integration limits in each relevant segment are different. Parameters ---------- x : array_like, float x coordinates xi, xj : array_like, float x values to from which to determine segments. Returns ------- segment_both : list of single element lists Each sub-list is the start index of the segment that contains xi or xj. segment_xi_only : list of single element lists When xi and xj not in the same segment, segment_xi_only will be the segment that contains xi. segment_xj_only : list of single element lists When xi and xj not in the same segment, segment_xj_only will be the segment that contains xj. segments_between : list of single element lists When xi and xj not in the same segment, segments_between will be the segments in between xi and xj but not containing xi or xj. See Also -------- segment_containing_xi_also_containing_xj : Find segment of xi and xj. """ ix1, ix2 = segment_containing_xi_also_containing_xj(x,xi,xj) segment_both = [np.array([],dtype=int) for v in ix1] segment_xi_only = segment_both[:] segment_xj_only = segment_both[:] segments_between = segment_both[:] for i, (i1,i2) in enumerate(zip(ix1,ix2)): if len(i1)==0 or len(i2)==0: continue if i1[0]==i2[0]: segment_both[i] = np.insert(segment_both[i],0,i1[0]) # np.insert(segment_both[i],0,i1[0]) else: segment_xi_only[i]=np.insert(segment_xi_only[i],0,i1[0]) segment_xj_only[i]=np.insert(segment_xj_only[i],0,i2[0]) segments_between[i]=np.r_[i1[0]+1:i2[0]] # np.insert(segment_xi_only[i],0,i1[0]) # np.insert(segment_xj_only[i],0,i2[0]) # segments_between[i]=np.r_[i1[0]+1:i2[0]] return (segment_both, segment_xi_only, segment_xj_only, segments_between)
[docs]def convert_x1_x2_y1_y2_to_x_y(x1, x2, y1, y2): """Convert data defined at start and end of each segment to a line of data x1_x2_y1_y2 data is defined by x1[1:]==x2[:-1] Parameters ---------- x1, y1 : array_like, float x and y values at start of each segment. x2, y2 : array_like, float x and y values at end of each segment (note x1[1:]==x2[:-1]). Returns ------- x, y : 1d ndarray, float x and y data of continuous line that matches the x1_x2_y1_y2 data. See Also -------- convert_x_y_to_x1_x2_y1_y2 : Reverse of this function. Notes ----- Graphs showing x1_x2_y1_y2 data and x_y data are shown below. :: x1_x2_y1_y2 type data y y2[2] ^ /| | / | | y2[0] / | | /| / | | / | y1[2] / | | / | | | | / |y1[1] y2[1]| | | y1[0]/ |------------| | | | (0) | (1) | (2) | | | | | | |----------------------------------------------------------->x x1[0] x1[1] x1[2] x2[0] x2[1] x2[2] e.g. x1 = [0.0, 0.3, 0.7], y1 = [1, 1, 2] x2 = [0.3, 0.7, 1.0], y2 = [3, 1, 4] x_y_data y y[5] ^ /| | / | | y[1] / | | /| / | | / | y[4]/ | | / | | | | / |y[2] y[3]| | | y[0]/ |------------| | | | | | | | | | | | |----------------------------------------------------------->x x[0] x[1] x[3] x[5] x[2] x[4] e.g. x = [0.0, 0.3, 0.3, 0.7, 0.7, 1.0] y = [1.0, 3.0, 1.0, 1.0, 2.0, 4.0] """ #TODO: include an option to collapse segments where step changes are tiny #and where consecutive segments lie on a straight line see np.allclose with #atol and rtol. Maybe collapse close steps first and then check for # #straight lines. probably better to do it in a separate function e.g. #def tidy_up_x1_x2_y1_y2() and tidy_up_x_y(). x1 = np.asarray(x1) x2 = np.asarray(x2) y1 = np.asarray(y1) y2 = np.asarray(y2) n = len(x1) if not all(len(v) == n for v in [x2,y1,y2]): raise ValueError("x1, x2, y1, y2 must be of same length") if not np.allclose(x1[1:],x2[:-1]): raise ValueError("data is not x1_x2_y1_y2, i.e. x1[1:] != x2[:-1]") x = x1[:] x = np.append(x, x2[-1]) y = y1[:] y = np.append(y, y2[-1]) insert_index = np.where(y2[:-1]!=y1[1:])[0] x = np.insert(x, insert_index + 1, x2[insert_index]) y = np.insert(y, insert_index + 1, y2[insert_index]) return x, y
[docs]def convert_x_y_to_x1_x2_y1_y2(x, y): """Convert a line of data to data defined at start and end of each segment Parameters ---------- x1, y1 : array_like, float x and y values at start of each segment. x2, y2 : array_like, float x and y values at end of each segment (note x1[1:]==x2[:-1]). Returns ------- x1, y1 : 1d array, float x and y values at start of each segment. x2, y2 : 1d array, float x and y values at end of each segment (note x1[1:]==x2[:-1]). See Also -------- convert_x1_x2_y1_y2_to_x_y : Reverse of this function. Notes ----- Graphs showing x1_x2_y1_y2 data and x_y data are shown below. :: x_y_data y y[5] ^ /| | / | | y[1] / | | /| / | | / | y[4]/ | | / | | | | / |y[2] y[3]| | | y[0]/ |------------| | | | | | | | | | | | |----------------------------------------------------------->x x[0] x[1] x[3] x[5] x[2] x[4] e.g. x = [0.0, 0.3, 0.3, 0.7, 0.7, 1.0] y = [1.0, 3.0, 1.0, 1.0, 2.0, 4.0] x1_x2_y1_y2 type data y y2[2] ^ /| | / | | y2[0] / | | /| / | | / | y1[2] / | | / | | | | / |y1[1] y2[1]| | | y1[0]/ |------------| | | | (0) | (1) | (2) | | | | | | |----------------------------------------------------------->x x1[0] x1[1] x1[2] x2[0] x2[1] x2[2] e.g. x1 = [0.0, 0.3, 0.7], y1 = [1, 1, 2] x2 = [0.3, 0.7, 1.0], y2 = [3, 1, 4] """ #TODO: include an option to collapse segments where step changes are tiny #and where consecutive segments lie on a straight line see np.allclose with #atol and rtol. Maybe collapse close steps first and then check for # #straight lines. probably better to do it in a separate function e.g. #def tidy_up_x1_x2_y1_y2() and tidy_up_x_y(). x = np.asarray(x) y = np.asarray(y) if len(x)!=len(y): raise ValueError("x and y must be of same length") segs = np.where(x[:-1]!=x[1:])[0] x1 = x[segs] x2 = x[segs+1] y1 = y[segs] y2 = y[segs+1] return x1, x2, y1, y2
[docs]def pinterp_x1_x2_y1_y2(a, xi, **kwargs): """Linear interpolation using PolyLine; wrapper for interp_x1_x2_y1_y2. Parameters ---------- a : PolyLine object PolyLine to interpolate from. xi : float, 1d array of float Values to interpolate at. **kwargs : keyword arguments Keyword arguments will be passed through `interp_x1_x2_y1_y2`. Returns ------- A : 1d ndarray, float Interpolated y value corresponding to xi See Also -------- interp_x1_x2_y1_y2 : Wrapped function. """ return interp_x1_x2_y1_y2(a.x1, a.x2, a.y1, a.y2, xi, **kwargs)
[docs]def interp_x1_x2_y1_y2(x1,x2,y1,y2, xi, choose_max=False): """Linear interpolation of x1_x2_y1_y2 data x1_x2_y1_y2 data is defined by x1[1:]==x2[:-1] if xi is beyond bounds of x then the first or last value of y will be returned as appropriate Parameters ---------- x1, y1 : array_like, float x and y values at start of each segment x2, y2 : array_like, float x and y values at end of each segment (note x1[1:]==x2[:-1]) xi : array_like, float x values to interpolate at Returns ------- A : 1d ndarray, float Interpolated y value corresponding to xi. """ x1 = np.asarray(x1) x2 = np.asarray(x2) y1 = np.asarray(y1) y2 = np.asarray(y2) n = len(x1) if not all(len(v) == n for v in [x2,y1,y2]): raise ValueError("x1, x2, y1, y2 must be of same length") if not np.allclose(x1[1:],x2[:-1]): raise ValueError("data is not x1_x2_y1_y2, i.e. x1[1:] != x2[:-1]") xi = np.atleast_1d(xi) # if isinstance(xi, numbers.Number): # xi = np.array([xi]) # xi = np.asarray(xi) segs = segment_containing_xi(np.append(x1,x2[-1]), xi, subset = None, choose_max = choose_max) A = np.empty(len(segs)) #mask = np.logical_not(map(len,segs)) #A = [[] for v in segs] for i, v in enumerate(segs): if len(v)>0: j = v[0] A[i] = y1[j] + (y2[j] - y1[j]) / (x2[j] - x1[j]) * (xi[i] - x1[j]) else: if abs(xi[i]-x1[0]) <abs(xi[i] - x2[-1]): #xi[i] beyond 1st value A[i]=y1[0] else: #xi[i] is beyond last value A[i]=y2[-1] return A
[docs]def pinterp_x_y(a, xi, **kwargs): """Linear interpolation using PolyLine; wrapper for interp_x_y. Parameters ---------- a : PolyLine object PolyLine to interpolate from. xi : float, 1d array of float Values to interpolate at. **kwargs : keyword arguments Keyword arguments will be passed through `interp_x_y`. Returns ------- A : 1d ndarray, float Interpolated y value corresponding to xi See Also -------- interp_x_y : Wrapped function. """ return interp_x_y(a.x, a.y, xi, **kwargs)
[docs]def interp_x_y(x,y,xi, choose_max = False): """Linear interpolation of x_y data If xi is beyond bounds of x then the first or last value of y will be returned as appropriate. Parameters ---------- x, y : array_like, float x and y values. xi : array_like, float x values to interpolate at. Returns ------- A : 1d ndarray, float Interpolated y value corresponding to xi. """ x = np.asarray(x) y = np.asarray(y) if len(x)!=len(y): raise ValueError("x and y must be of same length") xi = np.atleast_1d(xi) # if isinstance(xi, numbers.Number): # xi = np.array([xi]) # xi = np.asarray(xi) segs = segment_containing_xi(x, xi, subset=None, choose_max=choose_max) A = np.empty(len(segs)) #mask = np.logical_not(map(len,segs)) #A = [[] for v in segs] for i, v in enumerate(segs): if len(v)>0: j = v[0] A[i] = y[j] + (y[j+1] - y[j]) / (x[j+1] - x[j]) * (xi[i] - x[j]) else: if abs(xi[i] - x[0]) < abs(xi[i] - x[-1]): #xi[i] beyond 1st value A[i]=y[0] else: #xi[i] is beyond last value A[i]=y[-1] return A
[docs]def remove_superfluous_from_x_y(x, y, atol=1e-08): """Remove points that are on a line between other points Intermediate points are judged to be 'on a line' and therefore superfluous when the distance of the point from the line is below `atol`. Parameters ---------- x, y : 1d array_like, float x and y values. atol : float, optional atol is the threshold. Default atol=1e-8. Returns ------- xnew, ynew : 1d ndarray, float Cleaned up x and y values. Notes ----- #TODO: put in equation for distance of point to a line from wolfram http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html """ n = len(x) if n!=len(y): raise ValueError("x and y must be of sam length, " "{} vs {}".format(n, len(y))) x = np.asarray(x) y = np.asarray(y) if n<=2: return x, y ikeep = list(range(len(x))) j = 0 x0 = x[j] y0 = y[j] for i in range(2,n): x1=x[i] y1=y[i] dx = x1-x0 dy = y1-y0 d = math.sqrt(dx**2+dy**2) if np.allclose(d,0): ikeep.remove(i-1) continue di = abs((dx*(y0-y[j+1:i])-dy*(x0-x[j+1:i]))/d) if np.any(di>atol): #points not on line anymore j = i - 1 x0 = x[j] y0 = y[j] else: #points on line ikeep.remove(i-1) return x[ikeep], y[ikeep]
[docs]def pinterp_xa_ya_multipy_x1b_x2b_y1b_y2b(a, b, xai, xbi, **kwargs): """Interpolate a composite function made of two PolyLines; wrapper for interp_xa_ya_multipy_x1b_x2b_y1b_y2b. Evaluate f(xai)*g(xbi) where f(xa) and f(xb) are defined by PolyLine objects. Parameters ---------- a, b : PolyLine objects PolyLine objects that make up the separable interpolation function a(xa) * b(xb) x and y values of x_y part of interpolation function. xai : array_like, float x values to interpolate at for a part xbi : array_like, float x values to interpolate at for b part achoose_max : ``boolean``, optional If False (default), if xai falls on boundary of segments choose the minimum segment to interpolate within. bchoose_max : ``boolean``, optional If True (default), if xbi falls on boundary of segments choose the maximum segment to interpolate within. **kwargs : keyword arguments Keyword arguments such as achoose_max and bchoose_max that will be passed through to interp_xa_ya_multipy_x1b_x2b_y1b_y2b. See Also -------- interp_xa_ya_multipy_x1b_x2b_y1b_y2b : Wrapped Function. """ return interp_xa_ya_multipy_x1b_x2b_y1b_y2b(a.x, a.y, b.x1, b.x2, b.y1, b.y2, xai, xbi,**kwargs)
[docs]def interp_xa_ya_multipy_x1b_x2b_y1b_y2b(xa, ya, x1b, x2b, y1b, y2b, xai, xbi, achoose_max=False, bchoose_max=True): """Interpolate a composite function made of x_y and x1b_x2b_y1b_y2b piecewise-linear representations. Evaluate f(xai)*g(xbi) where f(xa) is defined with x_y data and g(xb) is defined by x1_x2_y1_y2 data. Does little calculation, mostly calls other functions. Calculates array A[len(xbi),len(xai)] Parameters ---------- xa, ya : 1d array_like, float x and y values of x_y part of interpolation function. x1b, y1b : array_like, float x and y values at start of each segment for x1_x2_y1_y2 part of interpolation function x2b, y2b : array_like, float x and y values at end of each segment for x1_x2_y1_y2 part of interpolation function (note x1[1:]==x2[:-1]) xai : array_like, float x values to interpolate at for x_y part xbi : array_like, float x values to interpolate at for x1_x2_y1_y2 part achoose_max : ``boolean``, optional If False (default), if xai falls on boundary of segments choose the minimum segment to interpolate within. bchoose_max : ``boolean``, optional If True (default), if xbi falls on boundary of segments choose the maximum segment to interpolate within. See Also -------- interp_x_y : Interpolate the x_y part. interp_x1_x2_y1_y2 : Interpolate the x1_x2_y1_y2 part. """ yai = interp_x_y(xa, ya, xai, choose_max=achoose_max) ybi = interp_x1_x2_y1_y2(x1b, x2b, y1b, y2b, xbi, choose_max=bchoose_max) return ybi[:, np.newaxis] * yai[np.newaxis,:]
[docs]def pavg_x_y_between_xi_xj(a, xi, xj, **kwargs): """Average between xi and xj for PolyLine data; wrapper for avg_x_y_between_xi_xj. Parameters ---------- a : PolyLine object PolyLine containing data for averaging. xi, xj : array_like, float x values to interpolate between. Returns ------- A : 1d array of float Interpolated values. len(A)=len(xi) See Also -------- avg_x_y_between_xi_xj : Wrapped function. """ return avg_x_y_between_xi_xj(a.x, a.y, xi, xj, **kwargs)
[docs]def avg_x_y_between_xi_xj(x, y, xi, xj): """Average between xi and xj piecewise-linear x_y data Parameters ---------- x, y : 1d array_like, float x and y values of x_y part of interpolation function. xi, xj : array_like, float x values to interpolate between. Returns ------- A : 1d array of float Interpolated values. len(A)=len(xi) See Also -------- integrate_x_y_between_xi_xj : Integration is intemediate step in average calculation. """ xi = np.atleast_1d(xi) xj = np.atleast_1d(xj) return integrate_x_y_between_xi_xj(x, y, xi, xj) / (xj - xi)
[docs]def pintegrate_x_y_between_xi_xj(a, xi, xj, **kwargs): """Integrate PolyLine data between xi and xj; wrapper for integrate_x_y_between_xi_xj. Parameters ---------- a : PolyLine object PolyLine containing data for integrating. xi, xj : array_like, float x values to interpolate between. Returns ------- A : 1d array of float Interpolated values. len(A) == len(xi). See Also -------- integrate_x_y_between_xi_xj : Wrapped function. """ return integrate_x_y_between_xi_xj(a.x, a.y, xi, xj, **kwargs)
[docs]def integrate_x_y_between_xi_xj(x, y, xi, xj): """Integrate piecewise-linear x_y data between xi and xj Parameters ---------- x, y : 1d array_like, float x and y values for piecewise linear integration. xi, xj : array_like, float x values to interpolate between. Returns ------- A : 1d array of float Interpolated values. len(A) == len(xi) See Also -------- interp_x_y : Interpolate the x_y part. segments_between_xi_and_xj : Line segments between xi and xj. """ x = np.asarray(x) y = np.asarray(y) xi = np.atleast_1d(xi) xj = np.atleast_1d(xj) (segment_both, segment_xi_only, segment_xj_only, segments_between) = segments_between_xi_and_xj(x, xi, xj) yi = interp_x_y(x, y, xi, choose_max = True) yj = interp_x_y(x, y, xj, choose_max = False) A = np.zeros(len(xi)) for i in range(len(xi)): for layer in segment_both[i]: A[i] += (yi[i] + yj[i]) * 0.5 * (xj[i] - xi[i]) for layer in segment_xi_only[i]: A[i] += (yi[i] + y[layer + 1]) * 0.5 * (x[layer + 1] - xi[i]) for layer in segments_between[i]: A[i] += (y[layer] + y[layer + 1]) * 0.5 * (x[layer + 1] - x[layer]) for layer in segment_xj_only[i]: A[i] += (y[layer] + yj[i]) * 0.5 * (xj[i] - x[layer]) return A
[docs]def pintegrate_x1_x2_y1_y2_between_xi_xj(a, xi, xj, **kwargs): """Integrate PolyLine data between xi and xj; wrapper for integrate_x1_x2_y1_y2_between_xi_xj. Parameters ---------- a : PolyLine object PolyLine containing data for integrating. xi, xj : array_like, float x values to integrate between. Returns ------- A : 1d array of float Results of integrations. len(A) == len(xi). See Also -------- integrate_x1_x2_y1_y2_between_xi_xj : Wrapped function. """ return integrate_x1_x2_y1_y2_between_xi_xj(a.x1, a.x2, a.y1, a.y2, xi, xj, **kwargs)
[docs]def integrate_x1_x2_y1_y2_between_xi_xj(x1, x2, y1, y2, xi, xj): """Integrate layered x1_x2_y1_y2 data between xi and xj Parameters ---------- x1, y1 : array_like, float x and y values at start of each segment. x2, y2 : array_like, float x and y values at end of each segment (note x1[1:]==x2[:-1]). xi, xj : array_like, float x values to integrate between. Returns ------- A : 1d array of float Results of integrations. len(A) == len(xi). See Also -------- interp_x1_x2_y1_y2 : Interpolation of x1_x2_y1_y2 data. segments_between_xi_and_xj : Line segments between xi and xj. """ x1 = np.asarray(x1) x2 = np.asarray(x2) y1 = np.asarray(y1) y2 = np.asarray(y2) xi = np.atleast_1d(xi) xj = np.atleast_1d(xj) x_for_interp = np.zeros(len(x1)+1) x_for_interp[:-1] = x1[:] x_for_interp[-1] = x2[-1] (segment_both, segment_xi_only, segment_xj_only, segments_between) = segments_between_xi_and_xj(x_for_interp, xi, xj) yi = interp_x1_x2_y1_y2(x1,x2,y1,y2,xi, choose_max = True) yj = interp_x1_x2_y1_y2(x1,x2,y1,y2,xj, choose_max = False) A = np.zeros(len(xi)) for i in range(len(xi)): for layer in segment_both[i]: A[i] += (yi[i] + yj[i]) * 0.5 * (xj[i] - xi[i]) for layer in segment_xi_only[i]: A[i] += (yi[i] + y2[layer]) * 0.5 * (x2[layer] - xi[i]) for layer in segments_between[i]: A[i] += (y1[layer] + y2[layer]) * 0.5 * (x2[layer] - x1[layer]) for layer in segment_xj_only[i]: A[i] += (y1[layer] + yj[i]) * 0.5 * (xj[i] - x1[layer]) return A
[docs]def pavg_x1_x2_y1_y2_between_xi_xj(a, xi, xj, **kwargs): """Average of PolyLine data between xi and xj; wrapper for avg_x1_x2_y1_y2_between_xi_xj. Parameters ---------- a : PolyLine object PolyLine containing data for integrating. xi, xj : array_like, float x values to integrate between. Returns ------- A : 1d array of float Average for each xi, xj pair. len(A) == len(xi). See Also -------- avg_x1_x2_y1_y2_between_xi_xj : Wrapped function. """ return avg_x1_x2_y1_y2_between_xi_xj(a.x1, a.x2, a.y1, a.y2, xi, xj, **kwargs)
[docs]def avg_x1_x2_y1_y2_between_xi_xj(x1, x2, y1, y2, xi, xj): """Average of x1_x2_y1_y2 data between xi and xj Parameters ---------- x1, y1 : array_like, float x and y values at start of each segment x2, y2 : array_like, float x and y values at end of each segment (note x1[1:]==x2[:-1]) xi, xj : array_like, float x values to integrate between Returns ------- A : 1d array of float Average for each xi, xj pair. len(A) == len(xi). See Also -------- integrate_x1_x2_y1_y2_between_xi_xj : Integration is intermediate step for average calculation. """ xi = np.atleast_1d(xi) xj = np.atleast_1d(xj) return integrate_x1_x2_y1_y2_between_xi_xj(x1, x2, y1, y2, xi, xj) / (xj - xi)
[docs]def pxa_ya_multipy_avg_x1b_x2b_y1b_y2b_between(a, b, xai, xbi, xbj, **kwargs): """Respectively interpolate at one point, and average between two points, the a composiute function of two PolyLine objects. Evaluate f(xai) * integrate[g(xb), (xbi, xbj)] / (xbi - xbj), where f(xa) and g(xb) are PolyLine objects; wrapper for xa_ya_multipy_avg_x1b_x2b_y1b_y2b_between. Parameters ---------- a, b : PolyLine object PolyLine object s containing data. The `b` Polyline will be averaged between xbi, and xbj; the `a` PolyLine will be interpolated at xai. xai : array_like, float x values to interpolate at for x_y part. xbi, xbj : array_like, float x values to average between for the x1_x2_y1_y2 part. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : 2d array of float Result of averageing and interpolating. shape(A) == (len(xbi), len(xai)). See Also -------- xa_ya_multipy_avg_x1b_x2b_y1b_y2b_between : Wrapped function. """ return xa_ya_multipy_avg_x1b_x2b_y1b_y2b_between(a.x,a.y,b.x1, b.x2, b.y1, b.y2, xai, xbi, xbj, **kwargs)
[docs]def xa_ya_multipy_avg_x1b_x2b_y1b_y2b_between(xa, ya, x1b, x2b, y1b, y2b, xai, xbi, xbj, achoose_max=False): """Respectively interpolate at one point, and average between two points, the x_y and x1_x2_y1_y2 portions of a composite function of piecewise-linear representations. Evaluate f(xai) * integrate[g(xb), (xbi, xbj)] / (xbi - xbj), where f(xa) is defined with x_y data and g(xb) is defined with x1_x2_y1_y2 data. Parameters ---------- xa, ya : 1d array_like, float x and y values of x_y part of function. x1b, y1b : array_like, float x and y values at start of each segment for x1_x2_y1_y2 part of function. x2b, y2b : array_like, float x and y values at end of each segment for x1_x2_y1_y2 part of function (note x1[1:]==x2[:-1]) xai : array_like, float x values to interpolate at for x_y part. xbi, xbj : array_like, float x values to average between for the x1_x2_y1_y2 part. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : 2d array of float Result. shape(A) == (len(xbi), len(xai)), i.e. rows represent See Also -------- interp_x_y : Interpolate the x_y part. avg_x1_x2_y1_y2_between_xi_xj : Average the x1_x2_y1_y2 part between xbi, xbj. """ yai = interp_x_y(xa, ya, xai, choose_max=achoose_max) ybi = avg_x1_x2_y1_y2_between_xi_xj(x1b, x2b, y1b, y2b, xbi, xbj) return ybi[:, np.newaxis] * yai[np.newaxis,:]
[docs]def pintegrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between(a, b, xi, xj, **kwargs): """Integrate between two points a composite function made of two PolyLine objects; wrapper for integrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between. Evaluate integrate[f(x)*g(x), (xi, xj)] where f(x) and g(x) are defined by PolyLine objects. The two PolyLines must have the same x values, i.e. the layers must match up, x1a==x1b, x2a==x2b. Parameters ---------- a, b : PolyLine object PolyLine objects multiplied together to define the composite function. x and y values at start of each segment for first x1_x2_y1_y2 part of composite function. xi, xj : array_like, float x values to integrate between. Returns ------- A : len(xi) 1d array of float Integrations for each xi, xj pair. See Also -------- integrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between : Wrapped function. polyline_make_x_common : Ensure PolyLine object have same x values. """ a, b = polyline_make_x_common(a, b) return integrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between(a.x1,a.x2,a.y1,a.y2,b.x1,b.x2,b.y1,b.y2,xi,xj, **kwargs)
[docs]def integrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between(x1a, x2a, y1a, y2a, x1b, x2b, y1b, y2b, xi, xj): """Integrate between two points a composite function made of two x1_x2_y1_y2 piecewise-linear data representations. Evaluate integrate[f(x)*g(x), (xi, xj)] where f(x) and g(x) are defined by x1_x2_y1_y2 data. The two data sets must have the same x values, i.e. the layers must match up, x1a==x1b, x2a==x2b. Parameters ---------- x1a, y1a : array_like, float x and y values at start of each segment for first x1_x2_y1_y2 part of composite function. x2a, y2a : array_like, float x and y values at end of each segment for first x1_x2_y1_y2 part of composite function (note x1[1:]==x2[:-1]). x1b, y1b : array_like, float x and y values at start of each segment for second x1_x2_y1_y2 part of composite function. x2b, y2b : array_like, float x and y values at end of each segment for second x1_x2_y1_y2 part of composite function (note x1[1:]==x2[:-1]). xi, xj : array_like, float x values to integrate between. Returns ------- A : len(xi) 1d array of float Results of integrations. """ x1a = np.asarray(x1a) x2a = np.asarray(x2a) y1a = np.asarray(y1a) y2a = np.asarray(y2a) x1b = np.asarray(x1b) x2b = np.asarray(x2b) y1b = np.asarray(y1b) y2b = np.asarray(y2b) if (not np.allclose(x1a, x1b)) or (not np.allclose(x2a, x2b)): #they may be different sizes raise ValueError("x values are different; they must be the same: \nx1a = {0}\nx1b = {1}\nx2a = {2}\nx2b = {3}".format(x1a,x1b, x2a, x2b)) #sys.exit(0) xi = np.atleast_1d(xi) xj = np.atleast_1d(xj) x_for_interp = np.zeros(len(x1a)+1) x_for_interp[:-1] = x1a[:] x_for_interp[-1] = x2a[-1] (segment_both, segment_xi_only, segment_xj_only, segments_between) = segments_between_xi_and_xj(x_for_interp, xi, xj) A = np.zeros(len(xi)) for i in range(len(xi)): for seg in segment_both[i]: a_slope = (y2a[seg] - y1a[seg]) / (x2a[seg] - x1a[seg]) b_slope = (y2b[seg] - y1b[seg]) / (x2b[seg] - x1b[seg]) A[i] += (-a_slope*b_slope*xi[i]**3/3 + a_slope*b_slope*xj[i]**3/3 - (-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)*xi[i]**2 + (-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)*xj[i]**2 - (a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg])*xi[i] + (a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg])*xj[i]) for seg in segment_xi_only[i]: a_slope = (y2a[seg] - y1a[seg]) / (x2a[seg] - x1a[seg]) b_slope = (y2b[seg] - y1b[seg]) / (x2b[seg] - x1b[seg]) A[i] += (a_slope*b_slope*x2a[seg]**3/3 - a_slope*b_slope*xi[i]**3/3 - (-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)*xi[i]**2 - (a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg])*xi[i] + x2a[seg]*(a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg]) + x2a[seg]**2*(-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)) for seg in segments_between[i]: a_slope = (y2a[seg] - y1a[seg]) / (x2a[seg] - x1a[seg]) b_slope = (y2b[seg] - y1b[seg]) / (x2b[seg] - x1b[seg]) A[i] += (-a_slope*b_slope*x1a[seg]**3/3 + a_slope*b_slope*x2a[seg]**3/3 - x1a[seg]*(a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg]) - x1a[seg]**2*(-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2) + x2a[seg]*(a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg]) + x2a[seg]**2*(-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)) for seg in segment_xj_only[i]: a_slope = (y2a[seg] - y1a[seg]) / (x2a[seg] - x1a[seg]) b_slope = (y2b[seg] - y1b[seg]) / (x2b[seg] - x1b[seg]) A[i] += (-a_slope*b_slope*x1a[seg]**3/3 + a_slope*b_slope*xj[i]**3/3 + (-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)*xj[i]**2 + (a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg])*xj[i] - x1a[seg]*(a_slope*b_slope*x1a[seg]**2 - a_slope*x1a[seg]*y1b[seg] - b_slope*x1a[seg]*y1a[seg] + y1b[seg]*y1a[seg]) - x1a[seg]**2*(-a_slope*b_slope*x1a[seg] + a_slope*y1b[seg]/2 + b_slope*y1a[seg]/2)) return A
[docs]def pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super(a,b,c, xai,xbi,xbj, **kwargs): """Superpose results of respectively interpolate at one point, and integrate between two points, the first PolyLine and the multiplied second and third PolyLine portions of a composite function of piecewise-linear representations. For each f(xa), g(xb) pair sum f(xai) * integrate[g(xb) * h(xb), (xbi, xbj)], where f(xa), g(xb), and h(xb) are defined with PolyLine objects. The function is a bit specialised in that `a` and `b` can be lists of PolyLine objects. The contribution of each composite function (a[0], b[0], c), (a[1], b[1], c), ... will be summed. The `b` and `c` PolyLine objects do NOT need to have the same x values, they will be forced to do so using `polyline_make_x_common`. Parameters ---------- a, b, c : PolyLine object, or list of PolyLine objects PolyLine obects making up the composite function. `a` and `b` can be lists of equal length. xai : array_like, float x values to interpolate the `a` part of the composite function. xbi, xbj : array_like, float x values to integrate the b * c part of the composite function. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : (len(xbi), len(xai)) 2d array Results for each xbi, xbj pair and xai value. See Also -------- xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Wrapped function. pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super : Same function with additional cosine term. pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Similar function without superposition. """ # b, c = polyline_make_x_common(b, c) # return xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a.x,a.y,b.x1,b.x2,b.y1,b.y2, c.x1, c.x2, c.y1, c.y2, xai,xbi,xbj, **kwargs) if not isinstance(a,list): a = [a] if not isinstance(b, list): b = [b] if len(a)!=len(b): raise ValueError("a and b must be lengths of equal length. len(a) = {0}, len(b) = {1}".format(len(a), len(b))) out = np.zeros((len(xbi), len(xai))) for aa, bb in zip(a, b): bb, cc = polyline_make_x_common(bb, c) out += xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(aa.x,aa.y,bb.x1,bb.x2,bb.y1,bb.y2, cc.x1, cc.x2, cc.y1, cc.y2, xai,xbi,xbj, **kwargs) return out
[docs]def pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a, b, c, xai,xbi,xbj, **kwargs): """Respectively interpolate at one point, and integrate between two points, the first PolyLine and the multiplied second and third PolyLine portions of a composite function of piecewise-linear representations. Evaluate f(xai) * integrate[g(xb) * h(xb), (xbi, xbj)], where f(xa), g(xb), and h(xb) are defined with PolyLine objects. The `b` and `c` PolyLine objects do NOT need to have the same x values, they will be forced to do so using `polyline_make_x_common`. Parameters ---------- a, b, c : PolyLine object PolyLine obects making up the composite function. xai : array_like, float x values to interpolate the `a` part of the composite function. xbi, xbj : array_like, float x values to integrate the b * c part of the composite function. between. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : (len(xbi), len(xai)) 2d array Results for each xbi, xbj pair and xai value. See Also -------- xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Wrapped Function. pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Similar function with additional cosine term. pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super : Same function with superposition. """ b, c = polyline_make_x_common(b, c) return xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a.x,a.y,b.x1,b.x2,b.y1,b.y2, c.x1, c.x2, c.y1, c.y2, xai,xbi,xbj, **kwargs)
[docs]def xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(xa,ya,x1b,x2b,y1b,y2b, x1c, x2c, y1c, y2c, xai,xbi,xbj, achoose_max=False): """Respectively interpolate at one point, and integrate between two points, the x_y and two multipled x1_x2_y1_y2 portions of a composite function of piecewise-linear representations. Evaluate f(xai) * integrate[g(xb) * h(xb), (xbi, xbj)], where f(xa) is defined with x_y data and g(xb) and h(xb) are defined with x1_x2_y1_y2 data. Parameters ---------- xa, ya : 1d array_like, float x and y values of x_y part of the composite function. x1b, y1b : array_like, float x and y values at start of each segment for the first x1_x2_y1_y2 part of the composite function. x2b, y2b : array_like, float x and y values at end of each segment for the first x1_x2_y1_y2 part of the composite function (note x1[1:]==x2[:-1]). x1c, y1c : array_like, float x and y values at start of each segment for the secondx1_x2_y1_y2 part of the composite function. x2c, y2c : array_like, float x and y values at end of each segment for the second x1_x2_y1_y2 part of the composite function (note x1[1:]==x2[:-1]). xai : array_like, float x values to interpolate the x_y part at. xbi, xbj : array_like, float x values to integrate the x1b_x2b_y1b_y2b * x1c_x2c_y1c_y2c part between. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : (len(xbi), len(xai)) 2d array Results for each xbi, xbj pair and xai value. See Also -------- pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Same function with PolyLine inputs. pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super : PolyLine inputs and superposition. xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Similar function with additional cosine term. """ yai = interp_x_y(xa, ya, xai, choose_max=achoose_max) ybi = integrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between(x1b, x2b, y1b, y2b, x1c, x2c, y1c, y2c, xbi, xbj) return ybi[:, np.newaxis] * yai[np.newaxis,:]
#def pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super(a,omega_phase, b,c, xai,xbi,xbj, **kwargs): # """wrapper for xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between to allow PolyLine input. # # Parameters # ---------- # omega_phase: 2 element tuple # (omega, phase) for use in cos(omega * t + phase) # # Notes # ----- # `a` and `b` `omega_phase` can be lists that will be superposed. This is not available in # the original function # # See Also # -------- # xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between # pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : similar polyline wrapper but no superposition # # """ # ## b, c = polyline_make_x_common(b, c) ## return xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a.x,a.y,b.x1,b.x2,b.y1,b.y2, c.x1, c.x2, c.y1, c.y2, xai,xbi,xbj, **kwargs) # # if not isinstance(a,list): # a = [a] # if not isinstance(b, list): # b = [b] # if not isinstance(b, list): # omega_phase = [omega_phase] # if len(a)!=len(b): # raise ValueError("a and b must be lengths of equal length. len(a) = {0}, len(b) = {1}".format(len(a), len(b))) # if len(a)!=len(omega_phase): # raise ValueError("a and omega_phase must be lengths of equal length. len(a) = {0}, len(omega_phase) = {1}".format(len(a), len(omega_phase))) # out = np.zeros((len(xbi), len(xai))) # for aa, bb, (omega, phase) in zip(a, b, omega_phase): # bb, cc = polyline_make_x_common(bb, c) # out += xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(aa.x,aa.y,omega, phase, bb.x1,bb.x2,bb.y1,bb.y2, cc.x1, cc.x2, cc.y1, cc.y2, xai,xbi,xbj, **kwargs) # return out
[docs]def pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super(a,b,c, xai, xbi, xbj, omega_phase=None, **kwargs): """Superpose results of respectively interpolate at one point, and integrate between two points, the cosine multiplied by PolyLine, and two multipled PolyLine portions of a composite function of piecewise-linear representations. For each f(xa), g(xb) pair sum f(xai) * cos(omega*xai + phase) * integrate[g(xb) * h(xb), (xbi, xbj)], where f(xa), g(xb), and h(xb) are PolyLine objects. The function is a bit specialised in that `a` and `b` and `omega_phase` can be lists of PolyLine objects. The contribution of each composite function (a[0], omega_phase[0], b[0], c), (a[1], b[1], omega_phase[1], c), ... will be summed. The `b` and `c` PolyLine objects do NOT need to have the same x values, they will be forced to do so using `polyline_make_x_common`. Parameters ---------- a, b, c : PolyLine object PolyLine obects making up the composite function. xai : array_like, float x values to interpolate the `a` part of the composite function. xbi, xbj : array_like, float x values to integrate the b * c part of the composite function. between. omega_phase : 2 element tuple, optional (omega, phase) for use in cos(omega * t + phase), Default omega_phase=None i.e. no cosine term. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : (len(xbi), len(xai)) 2d array Results for each xbi, xbj pair and xai value. See Also -------- xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Wrapped function pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super : Same function without cosine term. pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Similar function without superposition. """ # b, c = polyline_make_x_common(b, c) # return xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a.x,a.y,b.x1,b.x2,b.y1,b.y2, c.x1, c.x2, c.y1, c.y2, xai,xbi,xbj, **kwargs) if not isinstance(a,list): a = [a] if not isinstance(b, list): b = [b] if omega_phase is None: omega_phase = [None] * len(a) if not isinstance(b, list): omega_phase = [omega_phase] if len(a)!=len(b): raise ValueError("a and b must be lengths of equal length. len(a) = {0}, len(b) = {1}".format(len(a), len(b))) if len(a)!=len(omega_phase): raise ValueError("a and omega_phase must be lengths of equal length. len(a) = {0}, len(omega_phase) = {1}".format(len(a), len(omega_phase))) out = np.zeros((len(xbi), len(xai))) for aa, bb, om_ph in zip(a, b, omega_phase): bb, cc = polyline_make_x_common(bb, c) if not om_ph is None: omega, phase = om_ph out += xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(aa.x,aa.y,omega, phase, bb.x1,bb.x2,bb.y1,bb.y2, cc.x1, cc.x2, cc.y1, cc.y2, xai,xbi,xbj, **kwargs) else: out += xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(aa.x, aa.y, bb.x1, bb.x2, bb.y1,bb.y2, cc.x1, cc.x2, cc.y1, cc.y2, xai,xbi,xbj, **kwargs) return out
[docs]def pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a, omega, phase, b, c, xai,xbi,xbj, **kwargs): """Respectively interpolate at one point, and integrate between two points, the cosine multiplied by PolyLine, and two multipled PolyLine portions of a composite function of piecewise-linear representations. Evaluate f(xai) * cos(omega*xai + phase) * integrate[g(xb) * h(xb), (xbi, xbj)], where f(xa), g(xb), and h(xb) are PolyLine objects. The `b` and `c` PolyLine objects do NOT need to have the same x values, they will be forced to do so using `polyline_make_x_common`. Parameters ---------- a, b, c : PolyLine object PolyLine obects making up the composite function. omega, phase : float Values for defining cos(omega * t + phase). xai : array_like, float x values to interpolate the `a` part of the composite function. xbi, xbj : array_like, float x values to integrate the b * c part of the composite function. between. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : (len(xbi), len(xai)) 2d array Results for each xbi, xbj pair and xai value. See Also -------- xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Wrapped Function. pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Same function without the cosine part. pxa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super : Same function with superposition. """ b, c = polyline_make_x_common(b, c) return xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(a.x,a.y,omega, phase, b.x1,b.x2,b.y1,b.y2, c.x1, c.x2, c.y1, c.y2, xai,xbi,xbj, **kwargs)
[docs]def xa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between(xa, ya, omega, phase, x1b,x2b,y1b,y2b, x1c, x2c, y1c, y2c, xai,xbi,xbj, achoose_max=False): """Respectively interpolate at one point, and integrate between two points, the cosine multiplied by x_y, and two multipled x1_x2_y1_y2 portions of a composite function of piecewise-linear representations. Evaluate f(xai) * cos(omega*xai + phase) * integrate[g(xb) * h(xb), (xbi, xbj)], where f(xa) is defined with x_y data and g(xb) and h(xb) are defined with x1_x2_y1_y2 data. Parameters ---------- xa, ya : 1d array_like, float x and y values of x_y part of interpolation function. omega, phase : float Values for defining cos(omega * t + phase). x1b, y1b : array_like, float x and y values at start of each segment for the first 1st x1_x2_y1_y2 part of the composite function. x2b, y2b : array_like, float x and y values at end of each segment for the first 1st x1_x2_y1_y2 part of the composite function (note x1[1:]==x2[:-1]) x1c, y1c : array_like, float x and y values at start of each segment for the second x1_x2_y1_y2 part of the composite function. x2c, y2c : array_like, float x and y values at end of each segment for the second x1_x2_y1_y2 part of the composite function (note x1[1:]==x2[:-1]). xai : array_like, float x values to interpolate the xc_yc part at and evaluate the cos(omega*xai + phase) part. xbi, xbj : array_like, float x values to integrate the x1b_x2b_y1b_y2b * x1c_x2c_y1c_y2c part between. achoose_max : ``boolean``, optional If False (default), when xai falls on boundary of segments choose the minimum segment to interpolate within. Returns ------- A : (len(xbi), len(xai)) 2d array Results for each xbi, xbj pair and xai value. See Also -------- pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Same function with PolyLine inputs. pxa_ya_cos_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between_super : PolyLine inputs and superposition. xa_ya_multiply_integrate_x1b_x2b_y1b_y2b_multiply_x1c_x2c_y1c_y2c_between : Similar function without cosine term. """ xai = np.asarray(xai) yai = interp_x_y(xa, ya, xai, choose_max=achoose_max) * np.cos(omega * xai + phase) ybi = integrate_x1a_x2a_y1a_y2a_multiply_x1b_x2b_y1b_y2b_between(x1b, x2b, y1b, y2b, x1c, x2c, y1c, y2c, xbi, xbj) return ybi[:, np.newaxis] * yai[np.newaxis,:]
[docs]class PolyLine(object): """A Polyline is a series of x y points joined by straight lines Provides some extra functionality beyond just using an x array and y array to represent a multi-point line. Functinality of PolyLines: - PolyLines can be intialized in different ways e.g. use a single array of x-y points; use separate x-arrays and y-arrays; use x values at the start and end of each interval and y values at the start and end of each interval. This can be useful if you want to work with layer/interval data but to plot those intervals you want x-y points. - Multiply a PolyLine by a scalar and only the y values will be changed. - Add to/from a PolyLine with a scalar and only the y values will be changed. - PolyLines can be added together to create a new PolyLine; the x values of each PolyLine will be maintained. Any x values that are not common to both PolyLines will have their y values interpolated and then added. Parameters ---------- *args : array like A PolyLine object can be initialized with 1, 2, or 4 positional arguments (a different number of arguments will raise an error): - A single n-by-2 two-dimensional array of n (x, y) points. i.e. first column is x values, second column is y values. - Two one-dimensional arrays of equal length. The first array is the x values. The second array is the corresponding y values. - Four one-dimensional arrays of equal length. First array x1 is x values at the start of a interval. The second array x2 is x values at the end of a interval (note x1[1:] must equal x2[0:-1]). The third array y1 is the y values at the start of each interval. The fourth array y2 is the y values at the end of each interval. Attributes ---------- xy : 2d numpy array n by 2 array containing x and y values for each of the n points i.e. [[x0, y0], [x1, y1], ..., [xn, yn]]. x, y : 1d numpy array Arrays containing all the x values and all the y values in the PolyLine. x1, x2, y1, y2 : 1d numpy array When you want the start and end values of each of the intervals/layers in the PolyLine. x1 is the x values at the start of each interval, x2 is the x values at the end of each interval. y1 is the y values at the start of each interval, y2 is the y values at the end of each interval. Note that when dealing with intervals any vertical intervals will be lost e.g. say our x-y values are defined by joining the dots: PolyLine([0,1,1,2], [4,4,5,5]) is defined by 4 points but it will have only 2 layers/intervals i.e. x1 will be [0,1], x2 will be [1,0], y1 will be [4,5], y2 will be [4,5]. Just be careful when initialising PolyLines using x1_x2_y1_y2 values, any initial or final vertical section cannot be defined. x1_x2_y1_y2 : tuple of 4 1d arrays The x1, x2, y1, y2 arrays returned in a tuple. atol, rtol : float Absolute and relative tolerance when comparing equality of points in a PolyLine using numpy.allclose _prefix_for_numpy_array_repr : string When using the repr function on Numpy arrays the default output will print array([...]). Because PolyLines use numpy arrays to store data when using repr on PolyLines you would get PolyLine(array([...])). Now if in your code you have done import numpy.array as array" or some such import then you can just copy the repr of a PolylIne into you code . However I usually use "import numpy as np" so ideally I want 'np.' prepended to all my numpy array reprs. _prefix_for_numpy_array_repr does just this with the default prefix = "np." (if you wish to change the prefix for all numpy arrays not just the PolyLine repr then see numpy.`set_string_function` ) """ def __init__(self, *args): if not len(args) in [1,2,4]: #For the following error consider subclassing exception as per http://stackoverflow.com/a/1964247/2530083 raise TypeError('{:d} args given; Line can only be initialized with ' '1, 2, or 4 args: 1 (x-y coords), 2 (x-coords, y-coords), or ' '4 (x1-coords, x2-coords, y1-coords, y2-coords)'.format(len(args))) self._xy = None self._x = None self._y = None self._x1 = None self._x2 = None self._y1 = None self._y2 = None self.atol = 1e-5 self.rtol = 1e-8 self._prefix_for_numpy_array_repr = "np." if len(args)==1: #args[0] is an 2d array of n xy data points; shape=(n,2) self._xy = np.asarray(args[0], dtype=float) if ((self._xy.ndim != 2) or (self._xy.shape[0] < 2) or (self._xy.shape[1] != 2)): raise TypeError('x_y data must be 2d array_like with shape ' '(n, 2) with n >= 2. Yours has shape ' '({:d}, {:d}).'.format(*self._xy.shape)) return if len(args)==2: #args[0] and args[1] are 1d arrays of n x and y data points if ((len(args[0]) != len(args[1])) or (len(args[0]) < 2) or (len(args[1]) < 2)): raise TypeError('x and y must be of same length with at ' 'least two values. len(x)={:d}, ' 'len(y)={:d}'.format(len(args[0]), len(args[1]))) self._xy = np.empty([len(args[0]), 2], dtype=float) self._xy[:, 0]=args[0][:] self._xy[:, 1]=args[1][:] self._x = self._xy[:, 0] self._y = self._xy[:, 1] return if len(args)==4: #data is in segments with #args[0] and args[1] are 1d arrays of n x values at start and end of the n segments #args[2] and args[3] are 1d arrays of n y values at start and end of the n segments if len(set(map(len, args))) != 1: raise TypeError('x1, x2, y1, y2 must have same length. ' 'You have lengths ' '[{:d}, {:d}, {:d}, {:d}]'.format(*tuple(map(len, args)))) self._x1 = np.asarray(args[0], dtype=float) self._x2 = np.asarray(args[1], dtype=float) self._y1 = np.asarray(args[2], dtype=float) self._y2 = np.asarray(args[3], dtype=float) return @property def xy(self): """Get the xy values.""" if self._xy is None: x, y = convert_x1_x2_y1_y2_to_x_y(self.x1, self.x2, self.y1, self.y2) self._xy = np.empty([len(x), 2], dtype=float) self._xy[:,0] = x self._xy[:,1] = y return self._xy @property def x1_x2_y1_y2(self): """Get the x1_x2_y1_y2 values""" if self._x1 is None: self._x1, self._x2, self._y1, self._y2 = convert_x_y_to_x1_x2_y1_y2(self.x, self.y) return self._x1, self._x2, self._y1, self._y2 @property def x(self): """Get the x values of xy data.""" if self._x is None: if self._xy is None: self.xy self._x = self._xy[:, 0] return self._x @property def y(self): """Get the y values of xy data.""" if self._y is None: if self._xy is None: self.xy self._y = self._xy[:, 1] return self._y @property def x1(self): """Get the x1 values of x1_x2_y1_y2 data.""" return self.x1_x2_y1_y2[0] @property def x2(self): """Get the x2 values of x1_x2_y1_y2 data.""" return self.x1_x2_y1_y2[1] @property def y1(self): """Get the y1 values of x1_x2_y1_y2 data.""" return self.x1_x2_y1_y2[2] @property def y2(self): """Get the y2 values of x1_x2_y1_y2 data.""" return self.x1_x2_y1_y2[3] def __str__(self): """Return a string representation of the xy data""" return "PolyLine({})".format(str(self.xy)) # def __repr__(self): # """A string repr of the PolyLine that will recreate the PolyLine""" # return "PolyLine({}{})".format(self._prefix_for_numpy_array_repr, # repr(self.xy)) def __repr__(self): """A string repr of the PolyLine that will recreate the PolyLine""" return "PolyLine({})".format(repr(self.xy)) def __add__(self, other): return self._add_substract(other, op=operator.add) def __radd__(self, other): return self._add_substract(other, op=operator.add) def __sub__(self, other): return self._add_substract(other, op=operator.sub) def __rsub__(self, other): return self._add_substract(other, op=operator.sub).__mul__(-1) def __mul__(self, other): if isinstance(other, PolyLine): raise TypeError('Cannot multiply two PolyLines together. ' 'You will get a quadratic that I cannot handle.') sys.exit(0) try: return PolyLine(self.x, self.y * other) # a = copy.deepcopy(self) # a.xy #ensure xy has been initialized # a._xy[:,1] *= other except TypeError: print("unsupported operand type(s) for *: 'PolyLine' and " "'{}'".format(other.__class__.__name__)) sys.exit(0) # return a def __rmul__(self,other): return self.__mul__(other) def __truediv__(self, other): if isinstance(other, PolyLine): raise TypeError('Cannot divide two PolyLines together. You will get a quadratic that I cannot handle') sys.exit(0) try: return PolyLine(self.x, self.y / other) # a = copy.deepcopy(self) # a.xy #ensure xy has been initialized # a._xy[:,1] /= other except TypeError: print("Unsupported operand type(s) for /: 'PolyLine' and " "'{}'".format(other.__class__.__name__)) sys.exit(0) # return a def __rtruediv__(self, other): if isinstance(other, PolyLine): raise TypeError('Cannot divide two PolyLines together. You will get a quadratic that I cannot handle') sys.exit(0) try: return PolyLine(self.x, other / self.y) # a = copy.deepcopy(self) # a.xy #ensure xy has been initialized # a._xy[:,1] = other/a._xy[:,1] except TypeError: print("unsupported operand type(s) for /: 'PolyLine' and " "'{}'".format(other.__class__.__name__)) sys.exit(0) # return a return self.__mul__(other) def __eq__(self, other): if len(self.xy)!=len(other.xy): return False else: return np.allclose(self.xy, other.xy, rtol=self.rtol, atol=self.atol) def __ne__(self, other): return not self.__eq__(other) def _add_substract(self, other, op = operator.add): """Addition or subtraction of PolyLine objects""" mp = {operator.add: operator.iadd, operator.sub: operator.isub} iop = mp[op] if isinstance(other, PolyLine): if ((not (non_increasing(self.x) or non_decreasing(self.x))) or (not (non_increasing(other.x) or non_decreasing(other.x)))): raise TypeError('Your PolyLines have switchbacks in them; cannot add together.') sys.exit(0) #1. reverse values if decreasing if not is_initially_increasing(self.x): xa = self.x[::-1] ya = self.y[::-1] else: xa = self.x[:] ya = self.y[:] if not is_initially_increasing(other.x): xb = other.x[::-1] yb = other.y[::-1] else: xb = other.x[:] yb = other.y[:] xa, ya = remove_superfluous_from_x_y(xa, ya) xb, yb = remove_superfluous_from_x_y(xb, yb) data= np.zeros([len(xa)+len(xb),4]) data[:len(xa), 0]= xa[:] data[:len(xa), 1]= ya[:] data[:len(xa), 2]= 0 #line data[:len(xa), 3]= np.arange(len(xa)) #orig position data[len(xa):, 0]= xb[:] data[len(xa):, 1]= yb[:] data[len(xa):, 2]= 1 #line data[len(xa):, 3]= np.arange(len(xb)) #orig position data = data[np.lexsort((data[:,3],data[:,2],data[:,0]))] pnts = [] xnow =np.nan start = 0 stop = 0 i=0 while i < len(data): x = data[i, 0] #ind = np.where(np.abs(data[:,0] - xnow) < (self.atol + self.rtol * np.abs(xnow)))[0] ind = abs(data[:,0] - x) < (self.atol + self.rtol * abs(x)) j = np.where((ind) & (data[:, 2] == 0))[0] if len(j) > 0: #use point a line point y1 = data[j[0], 1] y1_= data[j[-1],1] else: #interpolate y1 = interp_x_y(xa, ya, x, choose_max=False) y1_= y1 j = np.where((ind) & (data[:, 2] == 1))[0] if len(j) > 0: #use point a line point y2 = data[j[0], 1] y2_= data[j[-1],1] else: #interpolate y2 = interp_x_y(xb, yb, x, choose_max=False) y2_= y2 y = op(y1, y2) y_ = op(y1_, y2_) pnts.append([x, y]) if abs(y - y_)> (self.atol + self.rtol * abs(y_)): pnts.append([x, y_]) i= np.nonzero(ind)[0][-1]+1 return PolyLine(pnts) try: return PolyLine(self.x, iop(self.y, other)) # a = copy.deepcopy(self) # #a._xy[:,1] += other # # a.xy #ensure xy has been initialized # iop(a._xy[:,1], other) except TypeError: print("Unsupported operand type(s) for +: 'PolyLine' and " "'{}'".format(other.__class__.__name__)) # sys.exit(0) return a
[docs]def polyline_make_x_common(*p_lines): """Add appropriate points to multiple PolyLine objetcs so that each has matching x1_x2 intevals. Parameters ---------- p_lines : PolyLine One or more instances of PolyLine. Returns ------- out : tuple of PolyLine Same number of Polyline's as `p_lines`. """ xa = [] ya = [] for i, line in enumerate(p_lines): if not isinstance(line, PolyLine): raise TypeError("p_lines[{:d}] is not a PolyLine".format(i)) sys.exit(0) if not (non_increasing(line.x) or non_decreasing(line.x)): raise TypeError('PolyLine #{:d} has switchbacks.'.format(i)) sys.exit(0) if not is_initially_increasing(line.x): xa.append(line.x[::-1]) ya.append(line.y[::-1]) else: xa.append(line.x[:]) ya.append(line.y[:]) if len(p_lines)==1: return p_lines[0] data= np.zeros([sum(map(len,xa)),4]) start = 0 for i, x in enumerate(xa): stop = start + len(x) data[start:stop, 0]= x[:] data[start:stop, 1]= ya[i][:] data[start:stop, 2]= i #line data[start:stop, 3]= np.arange(len(x)) #orig position start = stop data = data[np.lexsort((data[:,3],data[:,2],data[:,0]))] pnts = [[] for i in xa] xnow =np.nan start = 0 stop = 0 atol = p_lines[0].atol rtol = p_lines[0].rtol i=0 while i < len(data): x = data[i, 0] #ind = np.where(np.abs(data[:,0] - xnow) < (self.atol + self.rtol * np.abs(xnow)))[0] ind = abs(data[:,0] - x) < (atol + rtol * abs(x))# find all x values close to current x for k in range(len(xa)): j = np.where((ind) & (data[:, 2] == k))[0] if len(j) > 0: #use point a line point y1 = data[j[0], 1] y1_= data[j[-1],1] else: #interpolate y1 = interp_x_y(xa[k], ya[k], x, choose_max=False) y1_= y1 pnts[k].append([x,y1]) if abs(y1 - y1_)> (atol + rtol * abs(y1_)): pnts[k].append([x, y1_]) i = np.nonzero(ind)[0][-1]+1 return tuple(map(PolyLine, pnts))
[docs]def subdivide_x_y_into_segments(x, y, dx=None, min_segments = 2, just_before = None, logx=False, logy=False, logxzero=0.1, logyzero=0.1, rtol=1e-5, atol=1e-8): """Subdivide piecewise-linear line segment x, y data by interpolation Subsegments are evenly spaced in linear or log space. Parameters ---------- x : 1d array-like List of x values to subdivide. y : 1d array-like List of y values to subdivide (y subdivision is based on interpolation at the new x values). dx : float, optional Approximate length or log10(length) of subsegment. Say logx is True, segment x values are (1, 10), and x=0.2 then int((log(10)-log(1))/0.2)=5 subsegments will be created (i.e. 5 extra points will be inserted in the segment). The new x values will be will be 10**(log(1)+0.2), 10**(log(1)+0.4), 10**(log(1)+0.6) etc. Default dx=None i.e. no dx check and `min_segments` will be used. min_segments : int, optional Minuimum number of subsegments per inteval. This will be used if int(segment_lenght/dx)<min_segments. Default min_segments=2. just_before : float, optional If `just_before` is not None then in terms of subdividing, each segment will be treated as if it begins at its starting value but ends a distance of `just_before` multiplied by the interval length before its end value. This means that an extra point will be added just before all of the original points. Default just_before=None i.e dont use just before point. Use a small number e.g. 1e-6. `just_before` can be useful for example when getting times to evaluate pore pressure during a soil consoliation analysis. Say you have a PolyLine representing load_vs_time. If there are step changes in your load then there will be step changes in your pore pressure. To capture the step change in your output you need an output time just before and just after the step change in your load. Using `just_before` can achieve this. logx, logy : True/False, optional Use log scale on x and y axes. Default logx=logy=False. logxzero, logyzero : float, optional If log scale is used, force zero value to be given number. Useful when 1st point is zero but you really want to start from close to zero. Default logxzero=logyzero=0.01. rtol, atol : float, optional For determining equal to zero when using log scale with numpy. Default atol=1e-8, rtol = 1e-5. Returns ------- xnew, ynew : 1d array New x and y coordinates. See Also -------- subdivide_x_into_segments : Subdivide only x data. """ x = np.asarray(x, dtype=float) y = np.asarray(y, dtype=float) if len(x)!=len(y): raise ValueError('x and y must have same length ' 'len(x)={:d}, len(y)={:d}'.format(len(x), len(y))) if logx: x[np.abs(x) <= (atol + rtol * np.abs(x))] = logxzero if np.any(x<0): raise ValueError('When logx=True cannot have negative ' 'x values, x=' + x) x = np.log10(x) if logy: y[np.abs(y) <= (atol + rtol * np.abs(y))] = logyzero if np.any(y<0): raise ValueError('When logy=True cannot have negative ' 'y values, y=' + x) y = np.log10(y) if dx is None: dx = 2*(np.max(x)-np.min(x)) if just_before is None: xnew = [np.linspace(x1, x2, max(int(abs((x2-x1)//dx)), min_segments), endpoint = False) for (x1, x2) in zip(x[:-1], x[1:])] ynew = [np.linspace(y1, y2, max(int(abs((x2-x1)//dx)), min_segments), endpoint = False) for (x1, x2, y1, y2) in zip(x[:-1], x[1:], y[:-1], y[1:])] else: xnew = [np.linspace(x1, x1 + (1-just_before) * (x2 - x1), max(int(abs((x2-x1)//dx)), min_segments)+1, endpoint = True) for (x1, x2) in zip(x[:-1], x[1:])] ynew = [np.linspace(y1, y1 + (1-just_before) * (y2 - y1), max(int(abs((x2-x1)//dx)), min_segments)+1, endpoint = True) for (x1, x2, y1, y2) in zip(x[:-1], x[1:], y[:-1], y[1:])] # add in final point xnew.append([x[-1]]) ynew.append([y[-1]]) xnew = np.array([val for subl in xnew for val in subl]) ynew = np.array([val for subl in ynew for val in subl]) if logx: xnew = 10**xnew if logy: ynew = 10**ynew return (xnew, ynew)
[docs]def subdivide_x_into_segments(x, dx=None, min_segments=2, just_before = None, logx=False, logxzero=0.1, rtol=1e-5, atol=1e-8): """Subdivide sequential x values into subsegments. Intervals are evenly spaced in linear or log space Parameters ---------- x : 1d array-like List of x values to subdivide. dx : float, optional Approximate length or log10(length) of subsegment. Say logx is True, segment x values are (1, 10), and x=0.2 then int((log(10)-log(1))/0.2)=5 subsegments will be created (i.e. 5 extra points will be inserted in the segment). The new x values will be will be 10**(log(1)+0.2), 10**(log(1)+0.4), 10**(log(1)+0.6) etc. Default dx=None i.e. no dx check and `min_segments` will be used. min_segments : int, optional Minuimum number of subsegments per inteval. This will be used if int(segment_lenght/dx)<min_segments. Default min_segments=2. just_before : float, optional If `just_before` is not None then in terms of subdividing, each segment will be treated as if it begins at its starting value but ends a distance of `just_before` multiplied by the interval length before its end value. This means that an extra point will be added just before all of the original points. Default just_before=None i.e dont use just before point. Use a small number e.g. 1e-6. `just_before` can be useful for example when getting times to evaluate pore pressure during a soil consoliation analysis. Say you have a PolyLine representing load_vs_time. If there are step changes in your load then there will be step changes in your pore pressure. To capture the step change in your output you need an output time just before and just after the step change in your load. Using `just_before` can achieve this. logx : True/False, optional Use log scale on x data. Default logx=False. logxzero : float, optional If log scale is used, force zero value to be given number. Useful when 1st point is zero but you really want to start from close to zero. Default logxzero=0.01. rtol, atol : float, optional For determining equal to zero when using log scale with numpy. Default atol=1e-8, rtol = 1e-5. Returns ------- xnew : 1d array new x values See Also -------- subdivide_x_y_into_segments : Subdivide both x and y data, y data is interpolated from the new x values. """ x = np.asarray(x, dtype=float) if logx: x[np.abs(x) <= (atol + rtol * np.abs(x))] = logxzero if np.any(x<0): raise ValueError('When logx=True cannot have negative ' 'x values, x=' + x) x = np.log10(x) if dx is None: dx = 2*(np.max(x)-np.min(x)) if just_before is None: xnew = [np.linspace(x1, x2, max(int(abs((x2-x1)//dx)), min_segments), endpoint = False) for (x1, x2) in zip(x[:-1], x[1:])] else: xnew = [np.linspace(x1, x1 + (1-just_before) * (x2 - x1), max(int(abs((x2-x1)//dx)), min_segments)+1, endpoint = True) for (x1, x2) in zip(x[:-1], x[1:])] # add in final point xnew.append([x[-1]]) xnew = np.array([val for subl in xnew for val in subl]) if logx: xnew = 10**xnew return xnew
[docs]def layer_coords(h, segs, min_segs=1): """Divide layer heights into segments and calculate offset from start Subdivide each layer. Calculate offset. Useful when you are interested in the end points of each layer as well as some intermediate points. Parameters ---------- h : list/array 1d array of layer heights. segs : int Approximate number of segments to subdivide the whole profile into. min_segs : int, optional Minimum number of segments to subdivide a layer into. Default min_segs=2. Returns ------- z : 1d array Depths. Examples -------- >>> layer_coords([4], 2) array([0., 2., 4.]) >>> layer_coords([2], 1, 4) array([0. , 0.5, 1. , 1.5, 2. ]) >>> layer_coords([2,4,2], 5, 2) array([0., 1., 2., 4., 6., 7., 8.]) >>> layer_coords([5,1,5], 3, 2) array([ 0. , 2.5, 5. , 5.5, 6. , 8.5, 11. ]) """ h = np.asarray(h, dtype=float) z2 = np.cumsum(h) z1 = z2 - h dx = np.sum(h) / segs num = [max(height//dx, min_segs) for height in h] zlist = [np.linspace(z1_, z2_, num_, endpoint=False) for (z1_, z2_, num_) in zip(z1, z2, num)] zlist.append([z2[-1]]) z = np.array([val for subl in zlist for val in subl]) return z
[docs]def subdivide_into_elements(n=2, h=1.0, p=1, symmetry=True): """Subdivide a length into elements where sizes are proportional to adjacent elements. - element_0 = x * p**0 - element_1 = x * p**1 - element_2 = x * p**2 - etc. x is such that sum of `n` elements equals `h`. Parameters ---------- n : int, optional Number of elements to subdivide into. Default n=2. h : float, optional Length to subdivide. Default h=1. p : float, optional Ratio of subsequent elements. If p<1 then succesive elements will reduce in length. If p>1 then successive elements will increase in lenght. Default p=1 symmetry : True/False, optional If True then elements will be symmetrical abount middle. Default symmetry=True. Returns ------- out : array of float Length of each element. Should sum to `h`. See Also -------- np.logspace np.linspace subdivide_x_into_segments subdivide_x_y_into_segments Examples -------- >>> subdivide_into_elements(n=3, h=6.0, p=1, symmetry=True) array([2., 2., 2.]) >>> subdivide_into_elements(n=4, h=6.0, p=1, symmetry=True) array([1.5, 1.5, 1.5, 1.5]) >>> subdivide_into_elements(n=4, h=6.0, p=1, symmetry=False) array([1.5, 1.5, 1.5, 1.5]) >>> subdivide_into_elements(n=3, h=6.0, p=2, symmetry=True) array([1.5, 3. , 1.5]) >>> subdivide_into_elements(n=3, h=6.0, p=0.5, symmetry=True) array([2.4, 1.2, 2.4]) >>> subdivide_into_elements(n=4, h=6.0, p=2, symmetry=True) array([1., 2., 2., 1.]) >>> subdivide_into_elements(n=4, h=6.0, p=0.5, symmetry=True) array([2., 1., 1., 2.]) >>> subdivide_into_elements(n=4, h=6.0, p=0.5, symmetry=False) array([3.2, 1.6, 0.8, 0.4]) >>> subdivide_into_elements(n=3, h=3.5, p=2, symmetry=False) array([0.5, 1. , 2. ]) >>> sum(subdivide_into_elements(n=20, h=3.5, p=1.05, symmetry=False)) 3.5 """ if p<=0: raise ValueError("p must be greater than 0") if n<=1: raise ValueError("p must be an integer greater than 0") if p==1: return np.diff(np.linspace(0,h, n+1)) ppower = np.arange(n) if symmetry: if n%2==0: #even x = h x /= 2 * (p**(n / 2) - 1) / (p - 1) ppower[n // 2:] = np.arange(n / 2)[::-1] else: #odd x = h x /= 2 * (p**((n + 1) / 2) - 1) / (p - 1) - p**((n - 1) / 2) ppower[(n + 1) // 2:] = np.arange((n - 1) / 2)[::-1] else: x = h / (p**n - 1) * (p - 1) ppower = np.arange(n) return x*p**ppower
if __name__ == '__main__': import nose nose.runmodule(argv=['nose', '--verbosity=3', '--with-doctest']) # nose.runmodule(argv=['nose', '--verbosity=3'])