Skip to content

Commit d0d8f24

Browse files
author
Le-Nain-Capable
committed
New gaussian generation function !
You can now generate a gaussian for any task with autogenerated values. Both CDF and PDF. General fixes and improvements
1 parent 5a2c9d9 commit d0d8f24

5 files changed

Lines changed: 180 additions & 10 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
*autogenerated
33
GUI.py
44
.continue/
5+
test/
56

67
# Byte-compiled / optimized / DLL files
78
__pycache__/

URModbus/core/TimeTracker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ def run(self):
5757
with mutex:
5858
self.__end_time = time()
5959

60-
t = self.__end_time-self.__start_time-self.__pause_time-(2*Settings.ROBOT_SLEEP_TIME)
60+
t = self.__end_time-self.__start_time-self.__pause_time-(2*Settings.ROBOT_SLEEP_TIME) #2 0 task between tasks
6161

6262
if t > 0 :
6363
write_task_time(self.__process.name,
6464
last_task,
6565
self.__end_time,
66-
t) #2 0 task between tasks
66+
t)
6767

6868
self.__pause_time = 0
6969
last_task = current_task

URModbus/io/CommandServer.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from URModbus.core.RobotController import RobotController
88
from URModbus.core.ProcessTools import Process
99
from URModbus.core.TimeTracker import TimeTracker
10-
from URModbus.io.DataParser import read_data_file,calculate_mean_time,plot_gantt,cli_gantt
10+
from URModbus.io.DataParser import read_data_file,calculate_mean_time,plot_gantt,cli_gantt,gaussian
1111
import os
1212
import socket
1313
import threading
@@ -222,8 +222,8 @@ def __handle_command(self, cmd:str, args:list[str])->str:
222222
except ValueError:
223223
return "Task id must be an integer"
224224

225-
if any(task < 0 for task in tasks):
226-
return "Task id must be a positive integer"
225+
if any(task <= 0 for task in tasks):
226+
return "Task id must be a positive integer and not 0"
227227

228228
if not all(self.__process.isTaksInProcess(task) is True for task in tasks):
229229
return "One or more tasks are not in the process file"
@@ -236,11 +236,48 @@ def __handle_command(self, cmd:str, args:list[str])->str:
236236
else:
237237
text = cli_gantt(self.__process, tasks, name, car)
238238
return '\n'.join(text)
239-
239+
240+
elif cmd== "gaussian":
241+
if "-h" in args:
242+
text = ["This command allows you to draw a gaussian",
243+
"Usage: gaussian",
244+
"Special Parameters",
245+
" <tasks_ids>: plot only requested tasks. ex 1 2 3 4",
246+
" -name <name>: change the graph name"]
247+
return '\n'.join(text)
248+
249+
if "-name" in args:
250+
name_pos = args.index("-name")
251+
name = args[name_pos + 1]
252+
args.pop(name_pos + 1)
253+
args.remove("-name")
254+
else:
255+
name = ""
256+
257+
if len(args) > 0: #If tasks are passed, we must look fo task validity
258+
try:
259+
tasks = [int(task.strip()) for task in args]
260+
except ValueError:
261+
return "Task id must be an integer"
262+
263+
if any(task <= 0 for task in tasks):
264+
return "Task id must be a positive integer and not 0"
265+
266+
if not all(self.__process.isTaksInProcess(task) is True for task in tasks):
267+
return "One or more tasks are not in the process file"
268+
269+
else:
270+
tasks = self.__process.tasks[1:]
271+
272+
text = [gaussian(self.__process, task, name) for task in tasks]
273+
return '\n'.join(text)
274+
275+
240276
elif cmd == "help":
241277
text = ["add - add a task to pile",
242278
"clear - clear task pile",
243279
"gantt - generate a process gantt",
280+
"gaussian - draw a gaussian chart",
244281
"help - display help",
245282
"info - display info on a task",
246283
"load - load a new process file",

URModbus/io/DataParser.py

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import matplotlib
1616
matplotlib.use("Agg")
1717
import matplotlib.pyplot as plt
18+
from scipy.stats import norm
1819

1920

2021

@@ -266,11 +267,11 @@ def plot_gantt(process:Process, tasks: list[int]=[], plotName:str="")->str:
266267

267268
plt.tight_layout()
268269

269-
plt.savefig(f"./{Settings.AUTOGEN_DIR}/{process.name}/{process.name}.png")
270-
return f"Saved as {process.name}.png in ./{Settings.AUTOGEN_DIR}/{process.name}. Location: ./{Settings.AUTOGEN_DIR}/{process.name}/{process.name}.png"
270+
plt.savefig(f"./{Settings.AUTOGEN_DIR}/{process.name}/{process.name}.svg")
271+
return f"Saved as {process.name}.png in ./{Settings.AUTOGEN_DIR}/{process.name}. Location: ./{Settings.AUTOGEN_DIR}/{process.name}/{process.name}.svg"
271272

272273

273-
def cli_gantt(process, tasks: list[int] = [], plotName: str = "", fillcar: str = "█") -> list:
274+
def cli_gantt(process:Process, tasks: list[int] = [], plotName: str = "", fillcar: str = "█") -> list:
274275
"""
275276
Render a Gantt chart in the CLI with fixed column alignment.
276277
@@ -375,3 +376,134 @@ def cli_gantt(process, tasks: list[int] = [], plotName: str = "", fillcar: str =
375376

376377
return text
377378

379+
def gaussian(process:Process,task:int,plotName:str = "")->str:
380+
"""This function generates a gaussian chart of a process task, it is similar to gantt creation
381+
382+
Args:
383+
process (Process): Process used as a reference
384+
task (int): Number of the task
385+
plotName (str, optional): name of the plot. Defaults to "".
386+
387+
Returns:
388+
str: name of the saved plot
389+
"""
390+
391+
#first obtain mean and dev
392+
mean,dev,_,_ = calculate_mean_time(process.name,task)
393+
394+
if mean == 0.0 or dev == 0.0:
395+
return f"Task {task} - {process.get_task(task).name} - hold no exploitable time"
396+
397+
#Create the time axis from mean+/- 3 dev for >99% of case
398+
x = np.linspace(mean - 3*dev, mean + 3*dev, 1000)
399+
400+
# Calculate the probability density function (PDF) for the normal distribution
401+
pdf = norm.pdf(x, loc=mean, scale=dev)
402+
# Calculate the cumulative distribution function (CDF)
403+
cdf = norm.cdf(x, loc=mean, scale=dev)
404+
405+
fig, axes = plt.subplots(2, 1, figsize=(10, 10)) # Create a subplot of 2 figures
406+
407+
#Handle user redefining the name of the plot
408+
if plotName != "":
409+
axes[0].set_title(plotName)
410+
axes[1].set_title(plotName)
411+
else:
412+
axes[0].set_title(f'Task {task} Distribution PDF ({process.name})')
413+
axes[1].set_title(f'Task {task} Distribution CDF ({process.name})')
414+
415+
416+
#general settings for axis and grid
417+
axes[0].set_xlabel('Time (s)')
418+
axes[0].set_ylabel('Density')
419+
axes[0].grid(True, linestyle='--', alpha=0.7)
420+
421+
axes[1].set_xlabel('Time (s)')
422+
axes[1].set_ylabel('Cumulative Probability')
423+
axes[1].grid(True, linestyle='--', alpha=0.7)
424+
425+
426+
427+
# This is used to draw the color zones of the probability rules (68–95–99.7 rule) ---
428+
zones = [
429+
(0, 1, 'green', 0.2),
430+
(1, 2, 'orange', 0.15),
431+
(2, 3, 'red', 0.1),
432+
]
433+
434+
# This will draw the zones
435+
for i in [0, 1]:
436+
for start, end, color, alpha in zones:
437+
axes[i].axvspan(mean - end*dev, mean - start*dev, color=color, alpha=alpha)
438+
axes[i].axvspan(mean + start*dev, mean + end*dev, color=color, alpha=alpha)
439+
440+
# This will draw the vertical lines at special values, mark with red x
441+
# and add the little text on the graph
442+
for i in [0, 1]:
443+
# Plot mean and standard deviation lines
444+
axes[i].axvline(mean, color='red', linestyle='--', linewidth=1.5)
445+
446+
# Loop through multiples of standard deviation (n=-3 to n=3)
447+
for n in [-3, -2, -1, 0, 1, 2, 3]:
448+
val = mean + n * dev
449+
axes[i].axvline(val, color='black', linestyle=':', linewidth=1.0)
450+
451+
#Highlight the value
452+
axes[i].plot(val,
453+
(pdf if i == 0 else cdf)[np.searchsorted(x, val)],
454+
color='red',
455+
marker='x')
456+
457+
percent = (pdf if i == 0 else cdf)[np.searchsorted(x, val)]
458+
# Display the value text next to the vertical line for visualization
459+
axes[i].text(val + dev/10,
460+
np.max(pdf)/2 if i ==0 else 0.5,
461+
f'mean {'+' if n >=0 else '-'}{np.abs(n)}σ\n{val:.2f}s\n{(percent*100 if i==1 else percent):.2f}%',
462+
va='center',
463+
ha='left',
464+
fontsize=8,
465+
bbox=dict(boxstyle='round', facecolor='white', alpha=0.85))
466+
467+
# plot the gaussian curve
468+
469+
for i in [0, 1]:
470+
471+
# This can happen if you have a low mean with high deviation,
472+
# This is likelly a bad measurment
473+
if np.min(x)<0:
474+
# Dashed boundary at x = 0
475+
axes[i].axvline(0, color='black', linestyle='--', linewidth=1.2)
476+
477+
478+
#Plot the actual curves
479+
mask = x < 0
480+
notmask = x>0
481+
axes[i].plot(x[mask], (pdf if i == 0 else cdf)[mask],
482+
linestyle='dashed', color='blue')
483+
axes[i].plot(x[notmask], (pdf if i == 0 else cdf)[notmask],
484+
linestyle='solid', color='blue')
485+
486+
487+
#annotations
488+
text_str = (
489+
f"Task = {task}\n"
490+
f"Name = {process.get_task(task).name}\n"
491+
f"Mean = {mean:.2f}s\n"
492+
f"Std = {dev:.2f}s\n"
493+
f"Mean ± Std = [{mean-dev:.2f}s, {mean+dev:.2f}s]"
494+
)
495+
496+
for i in [0, 1]:
497+
axes[i].text(
498+
0.02, 0.95, text_str,
499+
transform=axes[i].transAxes,
500+
fontsize=10,
501+
verticalalignment='top',
502+
bbox=dict(boxstyle='round', facecolor='white', alpha=0.85)
503+
)
504+
505+
plt.tight_layout() # Adjust layout
506+
507+
plt.savefig(f"./{Settings.AUTOGEN_DIR}/{process.name}/gaussian_{task}.svg")
508+
return f"Saved as gaussian_{task}.png in ./{Settings.AUTOGEN_DIR}/{process.name}. Location: ./{Settings.AUTOGEN_DIR}/{process.name}/gaussian_{task}.svg"
509+

URModbus/io/TUI.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@ def format_pile(pile:list[int])->list:
4444
pile = [i for i in pile if i != 0]
4545

4646
if len(pile)>6: #if more than 6 tasks
47-
pile = pile[0:3] + ["..."] + pile[-3:]
47+
pile = pile[0:3] + [f'...{len(pile[3:-3])} hidden...'] + pile[-3:]
4848

4949
return pile

0 commit comments

Comments
 (0)