front.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. import math
  2. import streamlit as st
  3. import matplotlib.pyplot as plt
  4. import numpy as np
  5. import sigfig
  6. from streamlit_ace import st_ace
  7. from streamlit_echarts import st_echarts, JsCode
  8. # So that you can choose an interval of points on which we apply q-calc algorithm
  9. def plot_interact_abs_from_f(f, r, i, interval_range):
  10. if interval_range is None:
  11. interval_range = (0, 100)
  12. # fix the new file upload without echart interval refresh - dataZoom does not update it itself
  13. if 'interval_range' not in st.session_state:
  14. st.session_state.interval_range = (0, 100)
  15. interval_range = st.session_state.interval_range
  16. abs_S = list(abs(np.array(r) + 1j * np.array(i)))
  17. # echarts for datazoom https://discuss.streamlit.io/t/streamlit-echarts/3655
  18. # datazoom https://echarts.apache.org/examples/en/editor.html?c=line-draggable&lang=ts
  19. # axis pointer values https://echarts.apache.org/en/option.html#axisPointer
  20. options = {
  21. "xAxis": {
  22. "type": "category",
  23. "data": f,
  24. "name": "Hz",
  25. "nameTextStyle": {
  26. "fontSize": 16
  27. },
  28. "axisLabel": {
  29. "fontSize": 16
  30. },
  31. },
  32. "yAxis": {
  33. "type": "value",
  34. "name": "abs(S)",
  35. "nameTextStyle": {
  36. "fontSize": 16
  37. },
  38. "axisLabel": {
  39. "fontSize": 16
  40. },
  41. # "axisPointer": {
  42. # "type": 'cross',
  43. # "label": {
  44. # "show":"true",
  45. # "formatter": JsCode(
  46. # "function(info){console.log(info);return 'line ' ;};"
  47. # ).js_code
  48. # }
  49. # }
  50. },
  51. "series": [{
  52. "data": abs_S,
  53. "type": "line",
  54. "name": "abs(S)"
  55. }],
  56. "height":
  57. 300,
  58. "dataZoom": [{
  59. "type": "slider",
  60. "start": interval_range[0],
  61. "end": interval_range[1],
  62. "height": 100,
  63. "bottom": 10
  64. }],
  65. "tooltip": {
  66. "trigger": "axis",
  67. "axisPointer": {
  68. "type": 'cross',
  69. # "label": {
  70. # "show":"true",
  71. # "formatter": JsCode(
  72. # "function(info){console.log(info);return 'line ' ;};"
  73. # ).js_code
  74. # }
  75. }
  76. },
  77. "toolbox": {
  78. "feature": {
  79. # "dataView": { "show": "true", "readOnly": "true" },
  80. "restore": {
  81. "show": "true"
  82. },
  83. }
  84. },
  85. }
  86. # DataZoom event is not fired on new file upload. There are no default event to fix it.
  87. events = {
  88. "dataZoom":
  89. "function(params) { console.log('a');return ['dataZoom', params.start, params.end] }",
  90. "restore": "function() { return ['restore'] }",
  91. }
  92. # show echart with dataZoom and update intervals based on output
  93. get_event = st_echarts(options=options,
  94. events=events,
  95. height="500px",
  96. key="render_basic_bar_events")
  97. if not get_event is None:
  98. if get_event[0] == 'dataZoom':
  99. interval_range = get_event[1:]
  100. st.session_state.interval_range = interval_range
  101. else:
  102. if interval_range != (0, 100):
  103. interval_range = (0, 100)
  104. st.session_state.interval_range = interval_range
  105. st.experimental_rerun()
  106. # print(st.session_state.interval_range, interval_range)
  107. n = len(f)
  108. interval_start, interval_end = (int(n * interval_range[id] * 0.01)
  109. for id in (0, 1))
  110. return interval_range, interval_start, interval_end
  111. def circle(ax, x, y, radius, color='#1946BA'):
  112. from matplotlib.patches import Ellipse
  113. drawn_circle = Ellipse((x, y),
  114. radius * 2,
  115. radius * 2,
  116. clip_on=True,
  117. zorder=2,
  118. linewidth=2,
  119. edgecolor=color,
  120. facecolor=(0, 0, 0, .0125))
  121. ax.add_artist(drawn_circle)
  122. def plot_smith(r, i, g, r_cut, i_cut):
  123. show_excluded_points = True
  124. show_Abs_S_scale = False
  125. show_Re_Z_scale = False
  126. show_Im_Z_scale = False
  127. show_grid = True
  128. with st.expander("Smith chart options"):
  129. show_excluded_points = st.checkbox("Show excluded points",
  130. value=show_excluded_points)
  131. show_Abs_S_scale = st.checkbox("Show abs(S) lines",
  132. value=show_Abs_S_scale)
  133. show_Re_Z_scale = st.checkbox("Show Re(Z) lines",
  134. value=show_Re_Z_scale)
  135. show_Im_Z_scale = st.checkbox("Show Im(Z) lines",
  136. value=show_Im_Z_scale)
  137. show_grid = st.checkbox("Show grid", value=show_grid)
  138. fig = plt.figure(figsize=(10, 10))
  139. ax = fig.add_subplot()
  140. # major_ticks = np.arange(-1.0, 1.1, 0.25)
  141. minor_ticks = np.arange(-1.1, 1.1, 0.05)
  142. # ax.set_xticks(major_ticks)
  143. ax.set_xticks(minor_ticks, minor=True)
  144. # ax.set_yticks(major_ticks)
  145. ax.set_yticks(minor_ticks, minor=True)
  146. ax.grid(which='major', color='grey', linewidth=1.5)
  147. ax.grid(which='minor', color='grey', linewidth=0.5, linestyle=':')
  148. plt.xlabel('$Re(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
  149. plt.ylabel('$Im(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
  150. plt.title('Smith chart', fontsize=24, fontname="Cambria")
  151. # unit circle
  152. circle(ax, 0, 0, 1)
  153. if not show_grid:
  154. ax.axis('off')
  155. background_img_x = -1.981
  156. background_img_y = -1.949
  157. background_img_box = [
  158. background_img_x,
  159. background_img_x + 3.87,
  160. background_img_y,
  161. background_img_y + 3.87
  162. ]
  163. if show_Abs_S_scale:
  164. background = plt.imread("./source/frontend/images/s.png")
  165. background = ax.imshow(background, extent=background_img_box)
  166. if show_Re_Z_scale:
  167. background = plt.imread("./source/frontend/images/re(z).png")
  168. background = ax.imshow(background, extent=background_img_box)
  169. if show_Im_Z_scale:
  170. background = plt.imread("./source/frontend/images/im(z).png")
  171. background = ax.imshow(background, extent=background_img_box)
  172. # input data points
  173. if show_excluded_points:
  174. ax.plot(r, i, '+', ms=8, mew=2, color='#b6c7f4')
  175. # choosen data points
  176. ax.plot(r_cut, i_cut, '+', ms=8, mew=2, color='#1946BA')
  177. # circle approximation by calc
  178. radius = abs(g[1] - g[0] / g[2]) / 2
  179. x = ((g[1] + g[0] / g[2]) / 2).real
  180. y = ((g[1] + g[0] / g[2]) / 2).imag
  181. circle(ax, x, y, radius, color='#FF8400')
  182. XLIM = [-1.3, 1.3]
  183. YLIM = [-1.3, 1.3]
  184. ax.set_xlim(XLIM)
  185. ax.set_ylim(YLIM)
  186. st.pyplot(fig)
  187. # plot (abs(S))(f) chart with pyplot
  188. def plot_abs_vs_f(f, r, i, fitted_mag_s):
  189. fig = plt.figure(figsize=(10, 10))
  190. abs_S = list((r[n]**2 + i[n]**2)**0.5 for n in range(len(r)))
  191. xlim = [
  192. min(f) - abs(max(f) - min(f)) * 0.1,
  193. max(f) + abs(max(f) - min(f)) * 0.1
  194. ]
  195. ylim = [
  196. min(abs_S) - abs(max(abs_S) - min(abs_S)) * 0.5,
  197. max(abs_S) + abs(max(abs_S) - min(abs_S)) * 0.5
  198. ]
  199. ax = fig.add_subplot()
  200. ax.set_xlim(xlim)
  201. ax.set_ylim(ylim)
  202. ax.grid(which='major', color='k', linewidth=1)
  203. ax.grid(which='minor', color='grey', linestyle=':', linewidth=0.5)
  204. plt.xlabel(r'$f,\; 1/c$', color='gray', fontsize=16, fontname="Cambria")
  205. plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
  206. plt.title('Abs(S) vs frequency', fontsize=24, fontname="Cambria")
  207. ax.plot(f, abs_S, '+', ms=8, mew=2, color='#1946BA')
  208. ax.plot(f, fitted_mag_s, '-', linewidth=3, color='#FF8400')
  209. # radius = abs(g[1] - g[0] / g[2]) / 2
  210. # x = ((g[1] + g[0] / g[2]) / 2).real
  211. # y = ((g[1] + g[0] / g[2]) / 2).imag
  212. st.pyplot(fig)
  213. def run(calc_function):
  214. def is_float(element) -> bool:
  215. try:
  216. float(element)
  217. val = float(element)
  218. if math.isnan(val) or math.isinf(val):
  219. raise ValueError
  220. return True
  221. except ValueError:
  222. return False
  223. # to utf-8
  224. def read_data(data):
  225. for x in range(len(data)):
  226. if type(data[x]) == bytes:
  227. try:
  228. data[x] = data[x].decode('utf-8-sig', 'ignore')
  229. except:
  230. return 'Not an utf-8-sig line №: ' + str(x)
  231. return 'data read, but not parsed'
  232. # for Touchstone .snp format
  233. def parse_heading(data):
  234. nonlocal data_format_snp
  235. if data_format_snp:
  236. for x in range(len(data)):
  237. if data[x].lstrip()[0] == '#':
  238. line = data[x].split()
  239. if len(line) == 6:
  240. repr_map = {"ri": 0, "ma": 1, "db": 2}
  241. para_map = {"s": 0, "z": 1}
  242. hz_map = {
  243. "ghz": 10**9,
  244. "mhz": 10**6,
  245. "khz": 10**3,
  246. "hz": 1
  247. }
  248. hz, measurement_parameter, data_representation, _r, ref_resistance = (
  249. x.lower() for x in line[1:])
  250. try:
  251. return hz_map[hz], para_map[
  252. measurement_parameter], repr_map[
  253. data_representation], int(
  254. float(ref_resistance))
  255. except:
  256. break
  257. break
  258. return 1, 0, 0, 50
  259. # check if line has comments
  260. # first is a comment line according to .snp documentation,
  261. # others detects comments in various languages
  262. def check_line_comments(line):
  263. if len(line) < 2 or line[0] == '!' or line[0] == '#' or line[
  264. 0] == '%' or line[0] == '/':
  265. return None
  266. else:
  267. # generally we expect these chars as separators
  268. line = line.replace(';', ' ').replace(',', ' ')
  269. if '!' in line:
  270. line = line[:line.find('!')]
  271. return line
  272. # unpack a few first lines of the file to get number of ports
  273. def count_columns(data):
  274. return_status = 'data parsed'
  275. column_count = 0
  276. for x in range(len(data)):
  277. line = check_line_comments(data[x])
  278. if line is None:
  279. continue
  280. line = line.split()
  281. # always at least 3 values for single data point
  282. if len(line) < 3:
  283. return_status = 'Can\'t parse line № ' + \
  284. str(x) + ',\n not enough arguments (less than 3)'
  285. break
  286. column_count = len(line)
  287. break
  288. return column_count, return_status
  289. def prepare_snp(data, number):
  290. prepared_data = []
  291. return_status = 'data read, but not parsed'
  292. for x in range(len(data)):
  293. line = check_line_comments(data[x])
  294. if line is None:
  295. continue
  296. splitted_line = line.split()
  297. if number * 2 + 1 == len(splitted_line):
  298. prepared_data.append(line)
  299. elif number * 2 == len(splitted_line):
  300. prepared_data[-1] += line
  301. else:
  302. return_status = "Parsing error for .snp format on line №" + str(
  303. x)
  304. return prepared_data, return_status
  305. def unpack_data(data, first_column, column_count, ref_resistance,
  306. ace_preview_markers):
  307. nonlocal select_measurement_parameter
  308. nonlocal select_data_representation
  309. f, r, i = [], [], []
  310. return_status = 'data parsed'
  311. for x in range(len(data)):
  312. line = check_line_comments(data[x])
  313. if line is None:
  314. continue
  315. line = line.split()
  316. if column_count != len(line):
  317. return_status = "Wrong number of parameters on line № " + str(
  318. x)
  319. break
  320. # 1: process according to data_placement
  321. a, b, c = None, None, None
  322. try:
  323. a = line[0]
  324. b = line[first_column]
  325. c = line[first_column + 1]
  326. except:
  327. return_status = 'Can\'t parse line №: ' + \
  328. str(x) + ',\n not enough arguments'
  329. break
  330. if not ((is_float(a)) or (is_float(b)) or (is_float(c))):
  331. return_status = 'Wrong data type, expected number. Error on line: ' + \
  332. str(x)
  333. break
  334. # mark as processed
  335. # for y in (a,b,c):
  336. # ace_preview_markers.append(
  337. # {"startRow": x,"startCol": 0,
  338. # "endRow": x,"endCol": data[x].find(y)+len(y),
  339. # "className": "ace_stack","type": "text"})
  340. a, b, c = (float(x) for x in (a, b, c))
  341. f.append(a) # frequency
  342. # 2: process according to data_representation
  343. if select_data_representation == 'Frequency, real, imaginary':
  344. # std format
  345. r.append(b) # Re
  346. i.append(c) # Im
  347. elif select_data_representation == 'Frequency, magnitude, angle':
  348. r.append(b * np.cos(np.deg2rad(c)))
  349. i.append(b * np.sin(np.deg2rad(c)))
  350. elif select_data_representation == 'Frequency, db, angle':
  351. b = 10**(b / 20)
  352. r.append(b * np.cos(np.deg2rad(c)))
  353. i.append(b * np.sin(np.deg2rad(c)))
  354. else:
  355. return_status = 'Wrong data format'
  356. break
  357. # 3: process according to measurement_parameter
  358. if select_measurement_parameter == 'Z':
  359. # normalization
  360. r[-1] = r[-1] / ref_resistance
  361. i[-1] = i[-1] / ref_resistance
  362. # translate to S
  363. try:
  364. # center_x + 1j*center_y, radius
  365. p1, r1 = r[-1] / (1 + r[-1]) + 0j, 1 / (1 + r[-1]) #real
  366. p2, r2 = 1 + 1j * (1 / i[-1]), 1 / i[-1] #imag
  367. d = abs(p2 - p1)
  368. q = (r1**2 - r2**2 + d**2) / (2 * d)
  369. h = (r1**2 - q**2)**0.5
  370. p = p1 + q * (p2 - p1) / d
  371. intersect = [(p.real + h * (p2.imag - p1.imag) / d,
  372. p.imag - h * (p2.real - p1.real) / d),
  373. (p.real - h * (p2.imag - p1.imag) / d,
  374. p.imag + h * (p2.real - p1.real) / d)]
  375. intersect = [x + 1j * y for x, y in intersect]
  376. intersect_shift = [p - (1 + 0j) for p in intersect]
  377. intersect_shift = abs(np.array(intersect_shift))
  378. p = intersect[0]
  379. if intersect_shift[0] < intersect_shift[1]:
  380. p = intersect[1]
  381. r[-1] = p.real
  382. i[-1] = p.imag
  383. except:
  384. r.pop()
  385. i.pop()
  386. f.pop()
  387. if return_status == 'data parsed':
  388. if len(f) < 3 or len(f) != len(r) or len(f) != len(i):
  389. return_status = 'Choosen data range is too small, add more points'
  390. elif max(abs(np.array(r) + 1j * np.array(i))) > 2:
  391. return_status = 'Your data points have an abnormality:\
  392. they are too far outside the unit cirlce.\
  393. Make sure the format is correct'
  394. return f, r, i, return_status
  395. # make accessible a specific range of numerical data choosen with interactive plot
  396. # percent, line id, line id
  397. interval_range, interval_start, interval_end = None, None, None
  398. # info
  399. with st.expander("Info"):
  400. # streamlit.markdown does not support footnotes
  401. try:
  402. with open('./source/frontend/info.md') as f:
  403. st.markdown(f.read())
  404. except:
  405. st.write('Wrong start directory, see readme')
  406. # file upload button
  407. uploaded_file = st.file_uploader(
  408. 'Upload a file from your vector analizer. \
  409. Make sure the file format is .snp or it has a similar inner structure.'
  410. )
  411. # check .snp
  412. data_format_snp = False
  413. data_format_snp_number = 0
  414. if uploaded_file is None:
  415. st.write("DEMO: ")
  416. # display DEMO
  417. data_format_snp = True
  418. try:
  419. with open('./resource/data/8_default_demo.s1p') as f:
  420. data = f.readlines()
  421. except:
  422. # 'streamlit run' call in the wrong directory. Display smaller demo:
  423. data = [
  424. '# Hz S MA R 50\n\
  425. 11415403125 0.37010744 92.47802\n\
  426. 11416090625 0.33831283 92.906929\n\
  427. 11416778125 0.3069371 94.03318'
  428. ]
  429. else:
  430. data = uploaded_file.readlines()
  431. if uploaded_file.name[-4:-2] == '.s' and uploaded_file.name[-1] == 'p':
  432. data_format_snp = True
  433. data_format_snp_number = int(uploaded_file.name[-2])
  434. validator_status = '...'
  435. ace_preview_markers = []
  436. column_count = 0
  437. # data loaded
  438. circle_params = []
  439. if len(data) > 0:
  440. validator_status = read_data(data)
  441. if validator_status == 'data read, but not parsed':
  442. hz, select_measurement_parameter, select_data_representation, input_ref_resistance = parse_heading(
  443. data)
  444. col1, col2 = st.columns([1, 2])
  445. ace_text_value = ''.join(data).strip()
  446. with col1.expander("Processing options"):
  447. select_measurement_parameter = st.selectbox(
  448. 'Measurement parameter', ['S', 'Z'],
  449. select_measurement_parameter)
  450. select_data_representation = st.selectbox(
  451. 'Data representation', [
  452. 'Frequency, real, imaginary',
  453. 'Frequency, magnitude, angle', 'Frequency, db, angle'
  454. ], select_data_representation)
  455. if select_measurement_parameter == 'Z':
  456. input_ref_resistance = st.number_input(
  457. "Reference resistance:",
  458. min_value=0,
  459. value=input_ref_resistance)
  460. if not data_format_snp:
  461. input_hz = st.selectbox('Unit of frequency',
  462. ['Hz', 'KHz', 'MHz', 'GHz'], 0)
  463. hz_map = {
  464. "ghz": 10**9,
  465. "mhz": 10**6,
  466. "khz": 10**3,
  467. "hz": 1
  468. }
  469. hz = hz_map[input_hz.lower()]
  470. input_start_line = int(
  471. st.number_input("First line for processing:",
  472. min_value=1,
  473. max_value=len(data)))
  474. input_end_line = int(
  475. st.number_input("Last line for processing:",
  476. min_value=1,
  477. max_value=len(data),
  478. value=len(data)))
  479. data = data[input_start_line - 1:input_end_line]
  480. # Ace editor to show choosen data columns and rows
  481. with col2.expander("File preview"):
  482. # st.button(copy selection)
  483. # So little 'official' functionality in libs and lack of documentation
  484. # therefore beware: css hacks
  485. # yellow ~ ace_step
  486. # light yellow ~ ace_highlight-marker
  487. # green ~ ace_stack
  488. # red ~ ace_error-marker
  489. # no more good colors included in streamlit_ace for marking
  490. # st.markdown('''<style>
  491. # .choosen_option_1
  492. # {
  493. # color: rgb(49, 51, 63);
  494. # }</style>''', unsafe_allow_html=True)
  495. # markdown injection does not seems to work, since ace is in a different .html accessible via iframe
  496. # markers format:
  497. #[{"startRow": 2,"startCol": 0,"endRow": 2,"endCol": 3,"className": "ace_error-marker","type": "text"}]
  498. # add marking for choosen data lines TODO
  499. ace_preview_markers.append({
  500. "startRow": input_start_line - 1,
  501. "startCol": 0,
  502. "endRow": input_end_line,
  503. "endCol": 0,
  504. "className": "ace_highlight-marker",
  505. "type": "text"
  506. })
  507. st_ace(value=ace_text_value,
  508. readonly=True,
  509. auto_update=True,
  510. placeholder="Your file is empty",
  511. markers=ace_preview_markers,
  512. height="300px")
  513. if data_format_snp and data_format_snp_number >= 3:
  514. data, validator_status = prepare_snp(data,
  515. data_format_snp_number)
  516. if validator_status == "data read, but not parsed":
  517. column_count, validator_status = count_columns(data)
  518. f, r, i = [], [], []
  519. if validator_status == "data parsed":
  520. input_ports_pair = 1
  521. if column_count > 3:
  522. pair_count = (column_count - 1) // 2
  523. input_ports_pair = st.number_input(
  524. "Choosen pair of ports with network parameters:",
  525. min_value=1,
  526. max_value=pair_count,
  527. value=1)
  528. input_ports_pair_id = input_ports_pair - 1
  529. ports_count = round(pair_count**0.5)
  530. st.write(select_measurement_parameter +
  531. str(input_ports_pair_id // ports_count + 1) +
  532. str(input_ports_pair_id % ports_count + 1))
  533. f, r, i, validator_status = unpack_data(data,
  534. (input_ports_pair - 1) * 2 + 1,
  535. column_count,
  536. input_ref_resistance,
  537. ace_preview_markers)
  538. f = [x * hz for x in f] # to hz
  539. st.write("Use range slider to choose best suitable data interval")
  540. interval_range, interval_start, interval_end = plot_interact_abs_from_f(
  541. f, r, i, interval_range)
  542. f_cut, r_cut, i_cut = [], [], []
  543. if validator_status == "data parsed":
  544. f_cut, r_cut, i_cut = (x[interval_start:interval_end]
  545. for x in (f, r, i))
  546. with st.expander("Selected data interval as .s1p"):
  547. st_ace(value="# Hz S RI R 50\n" +
  548. ''.join(f'{f_cut[x]} {r_cut[x]} {i_cut[x]}\n'
  549. for x in range(len(f_cut))),
  550. readonly=True,
  551. auto_update=True,
  552. placeholder="Selection is empty",
  553. height="150px")
  554. if len(f_cut) < 3:
  555. validator_status = "Choosen interval is too small, add more points"
  556. st.write("Status: " + validator_status)
  557. if validator_status == "data parsed":
  558. col1, col2 = st.columns(2)
  559. check_coupling_loss = col1.checkbox(
  560. 'Apply correction for coupling loss')
  561. if check_coupling_loss:
  562. col1.write("Option: Lossy coupling")
  563. else:
  564. col1.write("Option: Cable attenuation")
  565. select_autoformat = col2.checkbox("Autoformat output", value=True)
  566. precision = None
  567. if not select_autoformat:
  568. precision = col2.slider("Precision",
  569. min_value=0,
  570. max_value=7,
  571. value=4)
  572. precision = '0.' + str(precision) + 'f'
  573. Q0, sigmaQ0, QL, sigmaQL, circle_params, fl, fitted_mag_s = calc_function(
  574. f_cut, r_cut, i_cut, check_coupling_loss)
  575. if Q0 <= 0 or QL <= 0:
  576. st.write("Negative Q detected, fitting may be inaccurate!")
  577. if select_autoformat:
  578. st.latex(
  579. r'Q_0 =' +
  580. f'{sigfig.round(Q0, uncertainty=sigmaQ0, style="PDG")}, ' +
  581. r'\;\;\varepsilon_{Q_0} =' +
  582. f'{sigfig.round(sigmaQ0 / Q0, sigfigs=1, style="PDG")}')
  583. st.latex(
  584. r'Q_L =' +
  585. f'{sigfig.round(QL, uncertainty=sigmaQL, style="PDG")}, ' +
  586. r'\;\;\varepsilon_{Q_L} =' +
  587. f'{sigfig.round(sigmaQL / QL, sigfigs=1, style="PDG")}')
  588. else:
  589. st.latex(r'Q_0 =' + f'{format(Q0, precision)} \pm ' +
  590. f'{format(sigmaQ0, precision)}, ' +
  591. r'\;\;\varepsilon_{Q_0} =' +
  592. f'{format(sigmaQ0 / Q0, precision)}')
  593. st.latex(r'Q_L =' + f'{format(QL, precision)} \pm ' +
  594. f'{format(sigmaQL, precision)}, ' +
  595. r'\;\;\varepsilon_{Q_L} =' +
  596. f'{format(sigmaQL / QL, precision)}')
  597. st.latex(r'f_L =' + f'{fl}' + 'Hz')
  598. with st.expander("Show static abs(S) plot"):
  599. plot_abs_vs_f(f_cut, r_cut, i_cut, fitted_mag_s)
  600. plot_smith(r, i, circle_params, r_cut, i_cut)