I compile the following graph with: dot graph.dot -Tpdf -ograph.pdf, producing:
The result is okay, but the state transition look a lot like a spagetthi monster and I have no clue what I could do to approach this problem. I tried other layouts: twopi, neato, etc. Are there parameters which allow it to force the graph to look a little bit more symmetric? Because the overall picture is okay.
To me it seems like the edges use the minimal space available to frame the edge descriptions, maybe this is the problem?
Is my graph design flawed? Should I write the different state transitions on one edge, using \n to separate the different transitions?
digraph finite_state_machine {
rankdir=LR;
edge [fontsize=26];
node [shape = doublecircle, width=2.0, fontsize=24, fixedsize=true,style=filled, colorscheme=spectral5]; New [fillcolor=3] Terminated [fillcolor=5];
node [shape = circle, width=2.0, fontsize=24, fixedsize=true, colorscheme=spectral5]; Runnable [fillcolor=4] Waiting [fillcolor=2] "Timed\nWaiting" [fillcolor=2] Blocked [fillcolor=1];
New -> Runnable [ label = "Thread.start" ];
Runnable -> Waiting [ label = "Object.wait" ];
Runnable -> Waiting [ label = "Thread.sleep" ];
Runnable -> Waiting [ label = "LockSupport.park" ];
Waiting -> Blocked [ label = "Reacquire monitor lock after\nleaving Object.wait" ]
Waiting -> Blocked [label = "Spurious wake-up"]
"Timed\nWaiting" -> Blocked [ label = "Reaquire monitor lock after\n leaving Object.wait" ]
"Timed\nWaiting" -> Terminated [ label = "Exception" ]
"Timed\nWaiting" -> Blocked [ label = "Spurious wake-up" ]
Runnable -> "Timed\nWaiting" [ label = "Object.wait" ];
Runnable -> Blocked [ label = "Contended Monitor\nEnter" ];
Blocked -> Runnable [ label = "Contended Monitor\nEntered" ];
Runnable -> Terminated [ label = "Thread finishes\nexecution" ]
Runnable -> Terminated [ label = "Exception" ]
Waiting -> Runnable [ label = "Object.notify\nObject.notifyAll" ]
Waiting -> Terminated [ label = "Exception" ]
"Timed\nWaiting" -> Runnable [ label = "Object.notify\nObject.notifyAll" ]
}
I don't think your design is flawed, I think it's ok. The dot syntax is readable and therefore maintainable, and the result is what an automatically generated graph looks typically alike.
Of course you can add small corrections to make this particular graph better (or at least different). Some of them may prove difficult to implement if for example the source for your graph is generated by an application. Here are some ideas:
To get the layout to be more symmetric, you may try to align the nodes Waiting and Terminated as well as Timed Waiting and Blocked by setting their group attribute to the same value (group=a and group=b).
It worked fine for Waiting and Teminated, but not so well for Timed Waiting and Blocked - probably because there are two edges between those nodes.
You may try to straighten them out by picking one of the edges which links them and set its weight attribute to a high value.
Other than that, I think the graph looks nicer over all because the edges are smoother and have less unnecessary curves, especially - but not only - between Runnable and Waiting.
The spaghetti effect is due to the splines - maybe it looks less spaghetti without splines? I tried by adding splines=compound or splines=ortho (same result):
The graph uses slightly less vertical space. It's not spaghetti, but it's not better in my opinion...
You may also try splines=compound without the group attributes, this should make the graph a little more compact (bug not necessarily prettier). Or simply play around with the weight of the edges to straighten out particularly unpleasant edges.
In some cases, concentrate can clear up graphs with lots of parallel edges - in this case it doesn't really help.
Related
I took this to the Godot Reddit first, but honestly it's not much help imo. A lot of questions go unanswered there. So here I am.
As the title says, Im making a quest system in godot 2d using nested dictionaries. Ive seen people use Nodes, Classes or otherwise for their quest systems, and a few dictionary based ones out there. I chose dictionaries as I know them the best(still isnt a whole lot, or i probably wouldnt be here asking this lol) And my quest system is set up like so:
QuestBase --> QuestHandler --> PlayerData
QuestBase is a giant dictionary of all available quests in the game.
PlayerData is a giant dictionary of all the player stats(including active, completed, or failed quests)
and QuestHandler takes a questName in a function to Copy the quest(questName) from QuestBase dict into the PlayerData.quests_active dict.(quests_active is a dictionary of quests(also dictionaries) inside of the PlayerData dictionary lol) But i cant seem to get it to work. I've done it a couple different ways now, including the way the Godot documentation states on how to add Dinctionaries into dictionaries. Please help, this is the error I get from QuestHandler:
Invalid set index 'tutorialQuest' (on base: 'Nil') with value of type 'Dictionary'
QuestBase:
var storyQuests = {
"tutorialQuest":{
"name" : "Your First Steps",#Name of the Quest
"desc" : "Get to know your environment and learn the ropes.",#Description of the Quest
"level" : 1, #Required level before player can accept quest
"questType" : 0, #0=Story Quest || 1=Side Quest || 2=Repeateable Quest
"taskType": 0, #0=Progressive(task must be completed in order) || 1=Synchronous(tasks can be completed in any order, and all at once)
"tasks":{#Dictionary of Quest's Tasks
"task1":{
"text":"Talk to Bjorn",#Text to display in Quest log
"type":0,#0=Talk,1=Slay,2=Fetch,3=Deliver,4=Collect,5=Goto
"quantity":0,#Determines the amount to complete task for Slay, Fetch, and Collect
"target":"Bjorn",#Determines Who to talk to, or who to slay, or what to collect.
"completed":false#Is this task complete?
},
"task2":{
"text":"Talk to Bjorn",
"type":"Talk",
"target":"Bjorn",
"completed":false
},
"task3":{
"text":"Talk to Bjorn",
"type":"Talk",
"target":"Bjorn",
"completed":false
}
},
"itemReward":{
"items":["Sword", "Basic Elixer"],#Names of items to give
"amount":[1, 5]#Amount of each item to give, respectively.
},
"expReward":{
"skill":["Forestry","Smithing"],#Names of skills to add Exp to
"amount": [100,100] #Amount to add to each skill, respectively.
#1st number will increase first skill in "skill", etc...
},
"Complete":false,
"Failed":false
}
}
PlayerData:
var playerData = {
... irrelevant player data...
#Quests
"quests_active": {"blank":"blank"},
"quests_completed": {},
"quests_failed": {},
and Finally, Quest Handler:
func startQuest(questName: String):
var active_quests = PlayerData.playerData.get("quests_active")
if QuestBase.storyQuests.has(questName):
var questCopy = QuestBase.storyQuests.get(questName).duplicate(true)
PlayerData.playerData.get("quests_active")[questName] = questCopy #.quests_active.append(questCopy)
#print("Story Quest Started: " + PlayerData.playerData.QuestsActive.get(questName).get("name"))
elif QuestBase.sideQuests.has(questName):
var questCopy = QuestBase.sideQuests.get(questName).duplicate(true)
active_quests.append(questCopy)
#print("Side Quest Started: " + PlayerData.playerData.QuestsActive.get(questName).get("name"))
else:
print("Quest Doesn't Exist! Check Spelling!")
I have a function that will download an image collection as a TFrecord or a geotiff.
Heres the function -
def download_image_collection_to_drive(collection, aois, bands, limit, export_format):
if collection.size().lt(ee.Number(limit)):
bands = [band for band in bands if band not in ['SCL', 'QA60']]
for aoi in aois:
cluster = aoi.get('cluster').getInfo()
geom = aoi.bounds().getInfo()['geometry']['coordinates']
aoi_collection = collection.filterMetadata('cluster', 'equals', cluster)
for ts in range(1, 11):
print(ts)
ts_collection = aoi_collection.filterMetadata('interval', 'equals', ts)
if ts_collection.size().eq(ee.Number(1)):
image = ts_collection.first()
p_id = image.get("PRODUCT_ID").getInfo()
description = f'{cluster}_{ts}_{p_id}'
task_config = {
'fileFormat': export_format,
'image': image.select(bands),
'region': geom,
'description': description,
'scale': 10,
'folder': 'output'
}
if export_format == 'TFRecord':
task_config['formatOptions'] = {'patchDimensions': [256, 256], 'kernelSize': [3, 3]}
task = ee.batch.Export.image.toDrive(**task_config)
task.start()
else:
logger.warning(f'no image for interval {ts}')
else:
logger.warning(f'collection over {limit} aborting drive download')
It seems whenever it gets to the second aoi it fails, Im confused by this as if ts_collection.size().eq(ee.Number(1)) confirms there is an image there so it should manage to get product id from it.
line 24, in download_image_collection_to_drive
p_id = image.get("PRODUCT_ID").getInfo()
File "/lib/python3.7/site-packages/ee/computedobject.py", line 95, in getInfo
return data.computeValue(self)
File "/lib/python3.7/site-packages/ee/data.py", line 717, in computeValue
prettyPrint=False))['result']
File "/lib/python3.7/site-packages/ee/data.py", line 340, in _execute_cloud_call
raise _translate_cloud_exception(e)
ee.ee_exception.EEException: Element.get: Parameter 'object' is required.
am I falling foul of immutable server side objects somewhere?
This is a server-side value, problem, yes, but immutability doesn't have to do with it — your if statement isn't working as you intend.
ts_collection.size().eq(ee.Number(1)) is a server-side value — you've described a comparison that hasn't happened yet. That means that doing any local operation like a Python if statement cannot take the comparison outcome into account, and will just treat it as a true value.
Using getInfo would be a quick fix:
if ts_collection.size().eq(ee.Number(1)).getInfo():
but it would be more efficient to avoid using getInfo more than needed by fetching the entire collection's info just once, which includes the image info.
...
ts_collection_info = ts_collection.getInfo()
if ts_collection['features']: # Are there any images in the collection?
image = ts_collection.first()
image_info = ts_collection['features'][0] # client-side image info already downloaded
p_id = image_info['properties']['PRODUCT_ID'] # get ID from client-side info
...
This way, you only make two requests per ts: one to check for the match, and one to start the export.
Note that I haven't actually run this Python code, and there might be some small mistakes; if it gives you any trouble, print(ts_collection_info) and examine the structure you actually received to figure out how to interpret it.
I would like to speed up figure generation in Bokeh by multiprocessing:
jobs = []
for label in list(peakLabels):
args = {'data': rt_proj_data[label],
'label': label,
'tools': tools,
'colors': itertools.cycle(palette),
'files': files,
'highlight': highlight}
jobs.append(args)
pool = Pool(processes=cpu_count())
m = Manager()
q = m.Queue()
plots = pool.map_async(plot_peaks_parallel, jobs)
pool.close()
pool.join()
def plot_peaks_parallel(args):
data = args['data']
label = args['label']
colors = args['colors']
tools = args['tools']
files = args['files']
highlight = args['highlight']
p = figure(title=f'Peak: {label}',
x_axis_label='Retention Time',
y_axis_label='Intensity',
tools=tools)
...
return p
Though I ran into this error:
MaybeEncodingError: Error sending result: '[Figure(id='1078', ...)]'. Reason: 'PicklingError("Can't pickle at 0x7fc7df0c0ea0>: attribute lookup ColumnDataSource. on bokeh.models.sources failed")'
Can I do something to the object p, so that it becomes pickleable?
Individual Bokeh objects are not serializable in isolation, including with pickle. The smallest meaningful unit of serialization in Bokeh is the Document, which is a specific collection of Bokeh objects guaranteed to be complete with respect to following references. However, I would be surprised if pickle works with Document either (AFAIK you are the first person to ask about it since the project started, it's never been a priority, or even looked into that I know of). Instead, I would suggest if you want to do something like this, to use Bokeh's own JSON serialization functions, such as json_item:
# python code
p_serialized = json.dumps(json_item(p))
This will properly serialize p in the context of the Document it is a part of. Then you can pass this to your page templates to display with the Bokeh JS embed API:
# javascript code
p = JSON.parse(p_serialized);
Bokeh.embed.embed_item(p, "mydiv")
I'm using GRAPH_TRAVERSAL to get the path from a list of nodes to the head of the tree. This works perfectly except when the example happens to be the head of the tree. In this case, the edgeCollection doesn't have an inbound entry for this object so it doesn't appear in the results.
FOR v IN GRAPH_TRAVERSAL('gdp2',
[{_id:'pmsite/14419285155'}],
'inbound',{edgeCollection:'child'})
RETURN v
The result is an empty list: []
Is there a way I can guarantee that the starting node is on the list? It would be a pain to go through the list of examples to segregate which ones are at the head of a tree.
The problem is within the query itself. It contains a subtle error which is hard to spot:
[{_id:pmsite/14419285155}],
This is missing the quotes around pmsite/14419285155.
What this query realy does is to devide (probably the count of) the collection pmsite by the id 14419285155 and put in this as {_id: divcount}.
If you add the missing quotes, the query should do exactly what you want there. (edit: quotes were present in the original query, fixed the post.)
hint: db._explain() gives information about that.
Trying to reproduce, using the knows sample graph:
arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
arangosh> var g = examples.loadGraph("knows_graph");
arangosh> db._query("FOR e IN GRAPH_TRAVERSAL('knows_graph', [{_id: 'persons/eve'}], 'inbound', {edgeCollection: 'knows'}) return e").toArray()
[
[
{
"vertex" : {
"_id" : "persons/eve",
"_rev" : "1405497100114",
"_key" : "eve",
"name" : "Eve"
}
}
]
]
However what creates a somewhat similar behaviour is to use a collection not part of the graph definition:
arangosh> db._create("othercol")
arangosh> db.othercol.save({_key: "1" })
arangosh> db._query("FOR e IN GRAPH_TRAVERSAL('knows_graph', [{_id: 'othercol/1'}], 'inbound', {edgeCollection: 'knows'}) return e").toArray()
[ ]
As pointed out in the Comments, edge relations have a direction. If you want to have edges pointing in both directions, you need to create a second relation in the other direction. Edges not fullfilling the edge definitions may be ignored.
Having trouble finalizing a dynamic QMenu tree.
The structure and format is perfect, but what remains missing is the return of all branch names when triggering the end-action.
The only implement I have tried with ANY trend toward a solution is the use of self.sender(); which returns only the name of the end-action.
Before adding a ton of the lengthy code snips - starting by conceptualizing the question seemed best in case there is some (obvious) means I am over-looking.
Example;
The ideal return based on the footer figure would be something along the lines of...
Top Image:
'Single Results' - 'Head Results'
Middle Image:
'Batch Results' - 'testBatch_vr3' - 'Run-1' - 'Budget Results'
Bottom Image:
'Single Results' - 'testBatch_vr3' - 'Run-3' - 'Particle Tracks'
To the point;
How can all names in a multi-leveled set of QMenus be retrieved when triggering end-action?
The following complex bits resolved my problem. It might be a bit obscure way to go about it (hovered signal from menu to search for dictionary menu entry) - but it works well for now.
# checks batch processing folder for existing directories and publishes the contents
# into the batch results menu comboBox
def populateBatchResults(self):
self.batchMenuDict = {}
self.runMenuDict = {}
self.runBatchResultsPopup.clear()
self.batchDirNamesMenu.clear()
batchModDir = self.estabBatchModelDir()
for batch in os.listdir(batchModDir):
fullBatchDir = batchModDir+str(batch)
if os.path.isdir(fullBatchDir):
self.batchMenuDict[batch] = QMenu(self.iface.mainWindow())
self.batchMenuDict[batch].setTitle(str(batch))
self.runBatchResultsPopup.addMenu(self.batchMenuDict[batch])
for run in os.listdir(fullBatchDir):
fullRunDir = fullBatchDir+'\\'+str(run)
if os.path.isdir(fullRunDir):
self.runMenuDict[run] = QMenu(self.iface.mainWindow())
self.runMenuDict[run].setTitle(str(run))
self.batchMenuDict[batch].addMenu(self.runMenuDict[run])
self.runMenuDict[run].hovered.connect(self.assertBatchMenuSelection)
# get all current cursor hovered menu names
def assertBatchMenuSelection(self):
self.selectedBatch = self.runBatchResultsPopup.activeAction().text()
self.selectedRun = self.batchMenuDict.get(self.selectedBatch).activeAction().text()
self.selectedAction = self.runMenuDict.get(self.selectedRun).activeAction().text()