analog-daddy 2.0.0__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.
- analog_daddy/__init__.py +0 -0
- analog_daddy/conf.py +47 -0
- analog_daddy/importer.py +315 -0
- analog_daddy/look_up.py +162 -0
- analog_daddy/utils.py +198 -0
- analog_daddy/write_config.py +180 -0
- analog_daddy-2.0.0.dist-info/METADATA +252 -0
- analog_daddy-2.0.0.dist-info/RECORD +11 -0
- analog_daddy-2.0.0.dist-info/WHEEL +5 -0
- analog_daddy-2.0.0.dist-info/licenses/LICENSE +661 -0
- analog_daddy-2.0.0.dist-info/top_level.txt +1 -0
analog_daddy/__init__.py
ADDED
|
File without changes
|
analog_daddy/conf.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
'''
|
|
3
|
+
This module contains the configuration/constants used throughout the project.
|
|
4
|
+
DO NOT WRITE THIS FILE MANUALLY. Instead use the write_config.py script
|
|
5
|
+
as follows:
|
|
6
|
+
|
|
7
|
+
from analog_daddy.write_config import write_config
|
|
8
|
+
write_config()
|
|
9
|
+
'''
|
|
10
|
+
# This file was generated on 10/11/2023 19:54:43 Zone: EST by write_config.py
|
|
11
|
+
# DON'T YOU DARE EDIT THIS FILE MANUALLY.
|
|
12
|
+
# Constants to avoid magic strings
|
|
13
|
+
PARAMETER_TAG = "Parameters:"
|
|
14
|
+
GM_KEY_START = "gm_"
|
|
15
|
+
LENGTH_KEY = "length"
|
|
16
|
+
GS_KEY = "gs"
|
|
17
|
+
DS_KEY = "ds"
|
|
18
|
+
SB_KEY = "sb"
|
|
19
|
+
DB_KEY = "db"
|
|
20
|
+
GB_KEY = "gb"
|
|
21
|
+
METHOD_KEY = "METHOD"
|
|
22
|
+
WARNING_KEY = "WARNING"
|
|
23
|
+
|
|
24
|
+
# Supply Voltage (Vdd)
|
|
25
|
+
VDD = 1.0
|
|
26
|
+
|
|
27
|
+
# for technologies that only allow stacking and multipliers.
|
|
28
|
+
# Is the length/width an integer?
|
|
29
|
+
IS_INTEGER = False
|
|
30
|
+
|
|
31
|
+
# Length Range
|
|
32
|
+
L_MIN = 4.5e-08
|
|
33
|
+
L_MAX = 1e-05
|
|
34
|
+
LENGTH_INCREMENT = 1e-08
|
|
35
|
+
# Width Range (Per-Finger)
|
|
36
|
+
W_MIN = 9e-08
|
|
37
|
+
W_MAX = 0.001
|
|
38
|
+
|
|
39
|
+
# Voltage Step Size = 20 mV. This is the default step size
|
|
40
|
+
# which will be selected for our voltage sweeps.
|
|
41
|
+
# Note that decreasing sizes would not lead to more accurate results
|
|
42
|
+
# since the lookup function interpolates.
|
|
43
|
+
VOLTAGE_STEP_SIZE = 0.02
|
|
44
|
+
|
|
45
|
+
# Decides the verbosity of the importer. If set to True, the importer will
|
|
46
|
+
# output information about the imported data in real-time.
|
|
47
|
+
IMPORTER_VERBOSE = True
|
analog_daddy/importer.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains functions for importing data from a pseudo CSV file.
|
|
3
|
+
"""
|
|
4
|
+
import csv
|
|
5
|
+
import numpy as np
|
|
6
|
+
from analog_daddy.conf import (LENGTH_KEY, GS_KEY,
|
|
7
|
+
DS_KEY, SB_KEY, GM_KEY_START,
|
|
8
|
+
PARAMETER_TAG, IMPORTER_VERBOSE)
|
|
9
|
+
|
|
10
|
+
def nest_populated_dictionary(y: dict, x: dict) -> dict:
|
|
11
|
+
"""
|
|
12
|
+
Takes a dictionary y with device-related keys, processes it to rearrange
|
|
13
|
+
keys based on unique devices, and nests dictionary x within each device key.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
- y (dict): Dictionary with device-related keys.
|
|
17
|
+
- x (dict): Dictionary of independent variables.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
- dict: Dictionary with independent variables nested within each device key.
|
|
21
|
+
"""
|
|
22
|
+
# Start by filtering keys that start with 'gm'
|
|
23
|
+
gm_keys = [key for key in y.keys() if key.startswith(GM_KEY_START)]
|
|
24
|
+
|
|
25
|
+
# Extract the device names by removing the 'gm_' prefix
|
|
26
|
+
unique_devices = {key.split(GM_KEY_START)[1] for key in gm_keys}
|
|
27
|
+
|
|
28
|
+
y_rearranged = {}
|
|
29
|
+
|
|
30
|
+
for device in unique_devices:
|
|
31
|
+
y_rearranged[device] = x.copy() # Nesting x within each device
|
|
32
|
+
for key in y.keys():
|
|
33
|
+
if device in key:
|
|
34
|
+
# Strip the device name (plus preceding underscore) to avoid redundancy
|
|
35
|
+
new_key = key.replace(f"_{device}", "")
|
|
36
|
+
y_rearranged[device][new_key] = y[key]
|
|
37
|
+
|
|
38
|
+
return y_rearranged
|
|
39
|
+
|
|
40
|
+
def extract_parameters_from_comment(comment: list) -> dict:
|
|
41
|
+
"""
|
|
42
|
+
Extract parameters from a comment list and return them as a dictionary.
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
- comment (list): The comment list containing the PARAMETER_TAG and the parameters.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
- dict: A dictionary of extracted parameters.
|
|
49
|
+
"""
|
|
50
|
+
parameters = {}
|
|
51
|
+
|
|
52
|
+
if not comment:
|
|
53
|
+
raise ValueError("The list is empty!")
|
|
54
|
+
|
|
55
|
+
if comment[0] == PARAMETER_TAG:
|
|
56
|
+
parts = comment[1].split(', ')
|
|
57
|
+
for part in parts:
|
|
58
|
+
try:
|
|
59
|
+
key, value = part.split('=')
|
|
60
|
+
parameters[key] = value.strip()
|
|
61
|
+
except ValueError:
|
|
62
|
+
raise ValueError("There are no parameters specified at all")
|
|
63
|
+
return parameters
|
|
64
|
+
|
|
65
|
+
def parse_value(value: str) -> float:
|
|
66
|
+
"""
|
|
67
|
+
Convert a string value with possible SI (International System of Units) prefixes
|
|
68
|
+
to its corresponding float value.
|
|
69
|
+
|
|
70
|
+
For instance:
|
|
71
|
+
- "1k" becomes 1000.0
|
|
72
|
+
- "1M" becomes 1000000.0
|
|
73
|
+
- "1u" becomes 0.000001
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
- value (str): The string value to parse. It may end with an SI prefix.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
- float: The parsed float value.
|
|
80
|
+
"""
|
|
81
|
+
# Dictionary to map SI prefixes to their multiplier values.
|
|
82
|
+
si_prefixes = {
|
|
83
|
+
'T': 1e12, # Tera
|
|
84
|
+
'G': 1e9, # Giga
|
|
85
|
+
'M': 1e6, # Mega
|
|
86
|
+
'k': 1e3, # Kilo
|
|
87
|
+
'm': 1e-3, # Milli
|
|
88
|
+
'u': 1e-6, # Micro
|
|
89
|
+
'n': 1e-9, # Nano
|
|
90
|
+
'p': 1e-12, # Pico
|
|
91
|
+
'f': 1e-15, # Femto
|
|
92
|
+
'a': 1e-18, # Atto (added)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# If the last character in the string is an SI prefix, multiply
|
|
96
|
+
# the number with the corresponding multiplier.
|
|
97
|
+
if value[-1] in si_prefixes:
|
|
98
|
+
return float(value[:-1]) * si_prefixes[value[-1]]
|
|
99
|
+
# If there's no SI prefix, simply convert the string to a float.
|
|
100
|
+
return float(value)
|
|
101
|
+
|
|
102
|
+
def strip_empty_strings(data: list) -> list:
|
|
103
|
+
"""
|
|
104
|
+
Remove empty strings, trim whitespaces, and adjust sublists based on the first element.
|
|
105
|
+
|
|
106
|
+
This function processes each inner list within the main data list,
|
|
107
|
+
removes empty string entries, trims whitespaces from non-empty strings,
|
|
108
|
+
and adjusts sublists by removing the first two elements if the first element
|
|
109
|
+
is not PARAMETER_TAG.
|
|
110
|
+
|
|
111
|
+
Parameters:
|
|
112
|
+
- data (list): The data list containing rows with possibly empty strings.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
- list: The processed data list with empty strings removed and sublists adjusted.
|
|
116
|
+
"""
|
|
117
|
+
cleaned_data = []
|
|
118
|
+
for sublist in data:
|
|
119
|
+
# Remove empty strings, trim whitespaces, and check the first element
|
|
120
|
+
cleaned_sublist = [item.strip() for item in sublist if item.strip() != '']
|
|
121
|
+
if cleaned_sublist and cleaned_sublist[0] != PARAMETER_TAG:
|
|
122
|
+
# If the first element is not PARAMETER_TAG, remove the first two elements
|
|
123
|
+
cleaned_sublist = cleaned_sublist[2:]
|
|
124
|
+
cleaned_data.append(cleaned_sublist)
|
|
125
|
+
return cleaned_data
|
|
126
|
+
|
|
127
|
+
class CSVReadingError(Exception):
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
def read_pseudo_csv_file(file_path: str) -> list:
|
|
131
|
+
"""
|
|
132
|
+
Read the pseudo CSV file and return its data as a list of lists.
|
|
133
|
+
|
|
134
|
+
Parameters:
|
|
135
|
+
- file_path (str): Path to the pseudo CSV file.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
- list: A list of rows, where each row is a list of string values.
|
|
139
|
+
"""
|
|
140
|
+
data = []
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
with open(file_path, 'r',encoding='utf-8') as csv_file:
|
|
144
|
+
reader = csv.reader(csv_file)
|
|
145
|
+
# Skip the first row (headers)
|
|
146
|
+
next(reader, None)
|
|
147
|
+
|
|
148
|
+
is_inside_parameters_section = False
|
|
149
|
+
current_parameters = None
|
|
150
|
+
|
|
151
|
+
for row in reader:
|
|
152
|
+
if len(row) == 0:
|
|
153
|
+
continue # Skip empty lines
|
|
154
|
+
|
|
155
|
+
if PARAMETER_TAG in row[0]:
|
|
156
|
+
is_inside_parameters_section = True
|
|
157
|
+
current_parameters = row[0].split(":")[1].strip()
|
|
158
|
+
elif is_inside_parameters_section:
|
|
159
|
+
data.append([PARAMETER_TAG, current_parameters])
|
|
160
|
+
is_inside_parameters_section = False
|
|
161
|
+
data.append(row)
|
|
162
|
+
else:
|
|
163
|
+
data.append(row)
|
|
164
|
+
|
|
165
|
+
except FileNotFoundError:
|
|
166
|
+
raise CSVReadingError(f"The file '{file_path}' was not found.")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
raise CSVReadingError(f"Error reading the CSV file: {str(e)}")
|
|
169
|
+
|
|
170
|
+
return data
|
|
171
|
+
|
|
172
|
+
def separate_lines(data: list) -> (list, list):
|
|
173
|
+
"""
|
|
174
|
+
Separate the provided data into parameter lines and data lines.
|
|
175
|
+
Parameters:
|
|
176
|
+
- data (list): The data list to process.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
- tuple: A tuple containing two lists - parameter lines and data lines.
|
|
180
|
+
"""
|
|
181
|
+
param_lines = [line for line in data if PARAMETER_TAG in line[0]]
|
|
182
|
+
data_lines = [line for line in data if PARAMETER_TAG not in line[0]]
|
|
183
|
+
return param_lines, data_lines
|
|
184
|
+
|
|
185
|
+
def process_parameters(param_lines: list) -> dict:
|
|
186
|
+
"""
|
|
187
|
+
Process the parameter lines to populate the x dictionary.
|
|
188
|
+
Parameters:
|
|
189
|
+
- param_lines (list): List of parameter lines.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
- dict: The x dictionary populated with parameters.
|
|
193
|
+
"""
|
|
194
|
+
x = {DS_KEY: [], GS_KEY: [], LENGTH_KEY: [], SB_KEY: []}
|
|
195
|
+
for line in param_lines:
|
|
196
|
+
_, params = line
|
|
197
|
+
for param in params.split(", "):
|
|
198
|
+
key, value = param.split("=")
|
|
199
|
+
parsed_value = parse_value(value) # Assuming values are floating point numbers
|
|
200
|
+
if parsed_value not in x[key]:
|
|
201
|
+
x[key].append(parsed_value)
|
|
202
|
+
return x
|
|
203
|
+
|
|
204
|
+
def create_data_structure(data_lines: list, x: dict) -> dict:
|
|
205
|
+
"""
|
|
206
|
+
Create the structure of the y dictionary based on x values.
|
|
207
|
+
Parameters:
|
|
208
|
+
- data_lines (list): List of data lines.
|
|
209
|
+
- x (dict): The x dictionary populated with parameters.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
- dict: The initialized y dictionary.
|
|
213
|
+
"""
|
|
214
|
+
y = {}
|
|
215
|
+
for key, _ in data_lines:
|
|
216
|
+
if key not in y:
|
|
217
|
+
y[key] = [
|
|
218
|
+
[
|
|
219
|
+
[
|
|
220
|
+
[None for _ in x[SB_KEY]]
|
|
221
|
+
for _ in x[LENGTH_KEY]
|
|
222
|
+
]
|
|
223
|
+
for _ in x[GS_KEY]
|
|
224
|
+
]
|
|
225
|
+
for _ in x[DS_KEY]
|
|
226
|
+
]
|
|
227
|
+
return y
|
|
228
|
+
|
|
229
|
+
def populate_data_values(combined_lines: list, param_lines: list, x: dict, y: dict) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Populate the y dictionary with data values using combined lines while leveraging
|
|
232
|
+
param_lines and data_lines for context.
|
|
233
|
+
Parameters:
|
|
234
|
+
- combined_lines (list): Combined list of parameter and data lines.
|
|
235
|
+
- param_lines (list): The separated parameter lines.
|
|
236
|
+
- data_lines (list): The separated data lines.
|
|
237
|
+
- x (dict): The x dictionary populated with parameters.
|
|
238
|
+
- y (dict): The initialized y dictionary.
|
|
239
|
+
"""
|
|
240
|
+
current_parameters = None
|
|
241
|
+
# go through the data line by line.
|
|
242
|
+
idx = 0
|
|
243
|
+
for line in combined_lines:
|
|
244
|
+
if IMPORTER_VERBOSE:
|
|
245
|
+
print(f"Processing line {idx}")
|
|
246
|
+
idx += 1
|
|
247
|
+
try:
|
|
248
|
+
# Check if it's a parameter line
|
|
249
|
+
if line in param_lines:
|
|
250
|
+
current_parameters = extract_parameters_from_comment(line)
|
|
251
|
+
ds_index = x[DS_KEY].index(parse_value(current_parameters[DS_KEY]))
|
|
252
|
+
gs_index = x[GS_KEY].index(parse_value(current_parameters[GS_KEY]))
|
|
253
|
+
length_index = x[LENGTH_KEY].index(parse_value(current_parameters[LENGTH_KEY]))
|
|
254
|
+
sb_index = x[SB_KEY].index(parse_value(current_parameters[SB_KEY]))
|
|
255
|
+
# else it's a data line.
|
|
256
|
+
else:
|
|
257
|
+
key, value = line
|
|
258
|
+
try:
|
|
259
|
+
y[key][ds_index][gs_index][length_index][sb_index] = float(value)
|
|
260
|
+
except ValueError:
|
|
261
|
+
# added to save it as a special values instead of
|
|
262
|
+
# just doing nothing.
|
|
263
|
+
if IMPORTER_VERBOSE:
|
|
264
|
+
print(f"""You got a problem at {ds_index},
|
|
265
|
+
{gs_index}, {length_index}, {sb_index}
|
|
266
|
+
Search for 123456789 in the final dict.""")
|
|
267
|
+
y[key][ds_index][gs_index][length_index][sb_index] = 123456789
|
|
268
|
+
|
|
269
|
+
except Exception as e:
|
|
270
|
+
if 'value' in locals():
|
|
271
|
+
raise ValueError(f"Error processing line {line} with value {value}: {e}")
|
|
272
|
+
else:
|
|
273
|
+
raise ValueError(f"Error processing line {line}: {e}")
|
|
274
|
+
|
|
275
|
+
def process_data(file_path: str) -> (dict, dict):
|
|
276
|
+
"""
|
|
277
|
+
Process the data from the provided file path to extract and organize parameters
|
|
278
|
+
and their associated values.
|
|
279
|
+
Parameters:
|
|
280
|
+
- file_path (str): The path to the pseudo CSV file containing the data.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
- tuple: A tuple containing two dictionaries (x, y) representing parameters
|
|
284
|
+
and their associated values.
|
|
285
|
+
"""
|
|
286
|
+
# Read the data from the file and strip empty strings
|
|
287
|
+
combined_lines = strip_empty_strings(read_pseudo_csv_file(file_path))
|
|
288
|
+
# Separate parameter lines and data lines
|
|
289
|
+
param_lines, data_lines = separate_lines(combined_lines)
|
|
290
|
+
# Process parameter lines to get x dictionary
|
|
291
|
+
x = process_parameters(param_lines)
|
|
292
|
+
if IMPORTER_VERBOSE:
|
|
293
|
+
print("Done Processing Parameters")
|
|
294
|
+
# Initialize the y dictionary based on x values
|
|
295
|
+
y = create_data_structure(data_lines, x)
|
|
296
|
+
if IMPORTER_VERBOSE:
|
|
297
|
+
print("Done Initializing Data Structure")
|
|
298
|
+
# Populate the y dictionary with data values
|
|
299
|
+
populate_data_values(combined_lines, param_lines, x, y)
|
|
300
|
+
return x, y
|
|
301
|
+
|
|
302
|
+
def lists_to_arrays(data):
|
|
303
|
+
"""
|
|
304
|
+
Recursively converts all lists in a dictionary to numpy arrays.
|
|
305
|
+
Args:
|
|
306
|
+
- data (dict): The dictionary to process.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
- dict: The processed dictionary with lists converted to numpy arrays.
|
|
310
|
+
"""
|
|
311
|
+
if isinstance(data, list): # Base case: if it's a list, convert to np.array
|
|
312
|
+
return np.array(data)
|
|
313
|
+
if isinstance(data, dict): # If it's a dictionary, process each key-value pair
|
|
314
|
+
return {key: lists_to_arrays(value) for key, value in data.items()}
|
|
315
|
+
return data # If it's neither a list nor a dictionary, return it as is
|
analog_daddy/look_up.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the look_up function, which is used to extract a desired
|
|
3
|
+
subset from the 4-dimensional simulation data and potentially interpolates if
|
|
4
|
+
the requested points aren't directly on the simulation grid.
|
|
5
|
+
"""
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.interpolate import RegularGridInterpolator, PchipInterpolator, interp1d
|
|
8
|
+
from analog_daddy.conf import (
|
|
9
|
+
LENGTH_KEY, GS_KEY, DS_KEY, SB_KEY,
|
|
10
|
+
DB_KEY, GB_KEY, METHOD_KEY, WARNING_KEY)
|
|
11
|
+
|
|
12
|
+
# don't warn user about bad divisions
|
|
13
|
+
np.seterr(divide="ignore", invalid="ignore")
|
|
14
|
+
|
|
15
|
+
def look_upVGS(data, **kwargs): #UNTESTED
|
|
16
|
+
# Set values either using kwargs.get
|
|
17
|
+
L = kwargs.get(LENGTH_KEY, min(data[LENGTH_KEY]))
|
|
18
|
+
vds = kwargs.get(DS_KEY, max(data[DS_KEY]) / 2)
|
|
19
|
+
vdb = kwargs.get(DB_KEY)
|
|
20
|
+
vgb = kwargs.get(GB_KEY)
|
|
21
|
+
gm_id = kwargs.get('gm_id')
|
|
22
|
+
id_w = kwargs.get('id_w')
|
|
23
|
+
vsb = kwargs.get(SB_KEY, 0)
|
|
24
|
+
method = kwargs.get(METHOD_KEY, 'pchip')
|
|
25
|
+
|
|
26
|
+
# Determine usage mode
|
|
27
|
+
if vgb is None and vdb is None:
|
|
28
|
+
mode = 1
|
|
29
|
+
elif vgb is not None and vdb is not None:
|
|
30
|
+
mode = 2
|
|
31
|
+
else:
|
|
32
|
+
print('Invalid syntax or usage mode!')
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
# Check whether GM_ID or ID_W was passed to function
|
|
36
|
+
if id_w is not None:
|
|
37
|
+
ratio_string = 'id_w'
|
|
38
|
+
ratio_data = id_w
|
|
39
|
+
elif gm_id is not None:
|
|
40
|
+
ratio_string = 'gm_id'
|
|
41
|
+
ratio_data = gm_id
|
|
42
|
+
else:
|
|
43
|
+
print('look_upVGS: Invalid syntax or usage mode!')
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
if mode == 1:
|
|
47
|
+
vgs = data[GS_KEY]
|
|
48
|
+
ratio = look_up(data, ratio_string, gs=vgs, ds=vds, sb=vsb, length=L)
|
|
49
|
+
else: # mode == 2
|
|
50
|
+
step = data[GS_KEY][0] - data[GS_KEY][1]
|
|
51
|
+
vsb = np.arange(max(data[SB_KEY]), min(data[SB_KEY]) - step, -step)
|
|
52
|
+
vgs = vgb - vsb
|
|
53
|
+
vds = vdb - vsb
|
|
54
|
+
ratio = look_up(data, ratio_string, gs=vgs, ds=vds, sb=vsb, length=np.ones(len(vsb)) * L)
|
|
55
|
+
|
|
56
|
+
# Interpolation loop
|
|
57
|
+
s = np.shape(ratio)
|
|
58
|
+
output = np.full((s[1], len(ratio_data)), np.nan)
|
|
59
|
+
for j in range(s[1]):
|
|
60
|
+
ratio_range = ratio[:, j]
|
|
61
|
+
VGS_range = vgs
|
|
62
|
+
if ratio_string == 'gm_id':
|
|
63
|
+
idx = np.argmax(ratio_range)
|
|
64
|
+
VGS_range = VGS_range[idx:]
|
|
65
|
+
ratio_range = ratio_range[idx:]
|
|
66
|
+
if max(ratio_data) > ratio_range[0]:
|
|
67
|
+
print('look_upVGS: gm_id input larger than maximum!')
|
|
68
|
+
f = interp1d(ratio_range, VGS_range, kind=method, bounds_error=False, fill_value=np.nan)
|
|
69
|
+
output[j, :] = f(ratio_data)
|
|
70
|
+
|
|
71
|
+
return output.flatten()
|
|
72
|
+
|
|
73
|
+
# result = look_upVGS(nch, GM_ID=[10,15], ds=0.6, sb=0.1, length=0.1)
|
|
74
|
+
|
|
75
|
+
def look_up(data, outvar, **kwargs):
|
|
76
|
+
"""
|
|
77
|
+
Extracts a desired subset from the 4-dimensional simulation data and
|
|
78
|
+
potentially interpolates if the requested points aren't directly on the simulation grid.
|
|
79
|
+
"""
|
|
80
|
+
# Making the outvar lowercase to avoid case sensitivity
|
|
81
|
+
outvar = outvar.lower()
|
|
82
|
+
# Extracting parameters from kwargs or using default values
|
|
83
|
+
length = kwargs.get(LENGTH_KEY, min(data[LENGTH_KEY]))
|
|
84
|
+
vgs = kwargs.get(GS_KEY, data[GS_KEY])
|
|
85
|
+
vds = kwargs.get(DS_KEY, max(data[DS_KEY])/2)
|
|
86
|
+
vsb = kwargs.get(SB_KEY, 0)
|
|
87
|
+
method = kwargs.get(METHOD_KEY, 'linear')
|
|
88
|
+
warning = kwargs.get(WARNING_KEY, 'on')
|
|
89
|
+
|
|
90
|
+
# Determine mode
|
|
91
|
+
if '_' in outvar and any('_' in key for key in kwargs.keys()):
|
|
92
|
+
# both output and input are ratios
|
|
93
|
+
mode = 3
|
|
94
|
+
elif '_' in outvar:
|
|
95
|
+
# output is a ratio
|
|
96
|
+
mode = 2
|
|
97
|
+
else:
|
|
98
|
+
# no ratio. simple lookup.
|
|
99
|
+
mode = 1
|
|
100
|
+
|
|
101
|
+
# Data extraction and interpolation for mode 1 and mode 2
|
|
102
|
+
if mode in [1, 2]:
|
|
103
|
+
|
|
104
|
+
# Extract ydata based on outvar
|
|
105
|
+
if '_' in outvar:
|
|
106
|
+
numerator, denominator = outvar.split('_')
|
|
107
|
+
# making ydata as a ratio as requested.
|
|
108
|
+
ydata = data[numerator] / data[denominator]
|
|
109
|
+
else:
|
|
110
|
+
ydata = data[outvar]
|
|
111
|
+
|
|
112
|
+
# Linear Interpolation using RegularGridInterpolator
|
|
113
|
+
if len(data[SB_KEY]) > 1:
|
|
114
|
+
interpolator = RegularGridInterpolator((data[DS_KEY], data[GS_KEY], data[LENGTH_KEY], data[SB_KEY]), ydata)
|
|
115
|
+
output = interpolator((vds, vgs, length, vsb))
|
|
116
|
+
else:
|
|
117
|
+
# If VSB isn't considered a dimension in data
|
|
118
|
+
interpolator = RegularGridInterpolator((data[DS_KEY], data[GS_KEY], data[LENGTH_KEY]), ydata)
|
|
119
|
+
output = interpolator((vds, vgs, length))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Mode 3
|
|
123
|
+
if mode == 3:
|
|
124
|
+
# extracting the invar from kwargs and making it lowercase to avoid case sensitivity
|
|
125
|
+
invar = (next(key for key in kwargs.keys() if '_' in key)).lower()
|
|
126
|
+
invals = kwargs[invar]
|
|
127
|
+
# Interpolating the outvar data across VGS
|
|
128
|
+
# both the outvar and invar are ratios hence making sure to use that ratio by splitting it.
|
|
129
|
+
outvar_numerator, outvar_denominator = outvar.split('_')
|
|
130
|
+
ydata_outvar = data[outvar_numerator] / data[outvar_denominator]
|
|
131
|
+
outvar_interpolator = RegularGridInterpolator((data[DS_KEY], data[GS_KEY], data[LENGTH_KEY], data[SB_KEY]), ydata_outvar)
|
|
132
|
+
invar_numerator, invar_denominator = invar.split('_')
|
|
133
|
+
ydata_invar = data[invar_numerator] / data[invar_denominator]
|
|
134
|
+
invar_interpolator = RegularGridInterpolator((data[DS_KEY], data[GS_KEY], data[LENGTH_KEY], data[SB_KEY]), ydata_invar)
|
|
135
|
+
|
|
136
|
+
vgs_vals = np.linspace(min(data[GS_KEY]), max(data[GS_KEY]), len(data[GS_KEY]))
|
|
137
|
+
outvar_values = outvar_interpolator((vds, vgs_vals, length, vsb))
|
|
138
|
+
invar_values = invar_interpolator((vds, vgs_vals, length, vsb))
|
|
139
|
+
|
|
140
|
+
if invar == "gm_id":
|
|
141
|
+
# If gm_id is passed as the third
|
|
142
|
+
# argument, special care is taken to look only "to the right" of the
|
|
143
|
+
# maximum gm/ID value in the gm/ID vs. VGS plane.
|
|
144
|
+
idx = np.argmax(invar_values)
|
|
145
|
+
invar_values = invar_values[idx:]
|
|
146
|
+
outvar_values = outvar_values[idx:]
|
|
147
|
+
|
|
148
|
+
elif invar in ["gm_cgg", "gm_css"]:
|
|
149
|
+
# If gm_cgg or gm_css is passed as the third
|
|
150
|
+
# argument, special care is taken to look only "to the left" of the
|
|
151
|
+
# maximum gm/cgg or gm/css value in the gm/cgg and gm/css vs. VGS plane.
|
|
152
|
+
idx = np.argmax(invar_values)
|
|
153
|
+
invar_values = invar_values[:idx+1]
|
|
154
|
+
outvar_values = outvar_values[:idx+1]
|
|
155
|
+
|
|
156
|
+
interpolator = PchipInterpolator(invar_values, outvar_values) if method == 'pchip' else interp1d(invar_values, outvar_values, kind=method)
|
|
157
|
+
output = interpolator(invals)
|
|
158
|
+
|
|
159
|
+
# Ensure the output is a column vector if one-dimensional
|
|
160
|
+
if output.ndim == 2 and min(output.shape) == 1:
|
|
161
|
+
output = output.flatten()
|
|
162
|
+
return output
|