今天的內容,可能是這個project最精實的部份呀(汗)!
在利用script進行自動化的時候,一個最困難的地方,是如何在新建一個甚至一群Entity的時候,給出適當的id,讓我們後續的script工作能夠更順利進行。
今天我們總共要建立下面幾個function:
get_ent_ids
_grab_id_ranges
_filter_id_ranges
_grab_filtered_id_range
_grab_id_range
get_fit_id_range
get_id
_grab_mix_id_range
get_fit_mix_id_range
get_mix_id
get_mat_prop_id
get_ent_ids透過base.CollectEntities來搜集整個ANSA database裡,某一個search_type的全部Entity,然候取出各Entity的id,回傳一個經過排序的list。
# id_grabbers.py
def get_ent_ids(search_type, deck=None):
deck = deck or constants.LSDYNA
ents = base.CollectEntities(deck, None, search_type)
return sorted(ent._id for ent in ents)
_grab_id_ranges 接收一個id的集合體叫ids,可以看作是get_ent_ids回傳的list。
ids是空的,我們就yield (1, 99999999, 99999999),代表當前可用的最小id為1,最大id為99999999,數量為99999999。如果不是空的話。我們就將ids丟進一個set,預防有重覆id出現的可能。0與100000000,方便我們做後續的計算。ids丟回一個list並進行排序。numpy的diff去得到ids裡面,每兩個數字的差距,命名為id_diff。id_diff打一個迴圈,如果裡面有任何大於1的情況,則代表其最少能夠插入一個id,我們就yield (最小可用id,最大可用id,數量)這個tuple。簡單來說,_grab_id_ranges會lazy的回傳每一個可用的id區間。
# id_grabbers.py
import numpy as np
def _grab_id_ranges(ids):
max_n = 1_0000_0000
min_n = 0
if not ids:
yield (min_n+1, max_n-1, max_n - min_n-1)
else:
ids = set(ids)
ids.add(min_n)
ids.add(max_n)
ids = sorted(ids)
id_diff = np.diff(ids)
for id_, diff_ in zip(ids, id_diff):
if diff_ > 1:
yield (id_+1, id_+diff_-1, diff_-1)
_filter_id_ranges幫助我們去找出可用的id段裡,lazy地回傳可用數量大於req_n的。
# id_grabbers.py
def _filter_id_ranges(req_n, id_ranges):
for start, end_, n in id_ranges:
if n >= req_n:
yield (start, end_)
_grab_filtered_id_range透過_grab_id_ranges得到可用的id ranges,並透過_filter_id_ranges回傳一個大於req_n的id range。
# id_grabbers.py
def _grab_filtered_id_range(req_n, ids):
id_ranges = _grab_id_ranges(ids)
filtered_id_range_iter = _filter_id_ranges(req_n, id_ranges)
return next(filtered_id_range_iter)
_grab_id_range回傳某search_type下,一個大於或等於req_n的id_range。
# id_grabbers.py
def _grab_id_range(req_n, search_type, deck=None):
ids = get_ent_ids(search_type, deck)
return _grab_filtered_id_range(req_n, ids)
get_fit_id_range回傳某search_type下,一個當下可用的fit id_range。
# id_grabbers.py
def get_fit_id_range(req_n, search_type, deck=None):
start, _ = _grab_id_range(req_n, search_type, deck)
return start, start+req_n
get_id回傳某search_type下,當下可用的最小id。
# id_grabbers.py
def get_id(search_type, deck=None):
start, _ = get_fit_id_range(1, search_type, deck)
return start
有時候,我們會想對齊兩種不同Entity的id,例如一連串相同id的material及property,此時_grab_mix_id_range可以幫助我們。
_grab_mix_id_range回傳同時考慮search_types情況下,一個可用的id range。
raise_for_not_put_in_a_container檢查search_types是否為iterable。get_ent_ids及set.union去找出search_types內各種type用過的id。_grab_filtered_id_range,回傳適合的id range。# id_grabbers.py
def _grab_mix_id_range(req_n, search_types, deck=None):
err_msg = f'{search_types=} might not be a suitable iterable.\n\
Try to put {search_types} in a list first.'
raise_for_not_put_in_a_container(
search_types, NotInContainerError, err_msg)
container = [set(get_ent_ids(type_, deck))
for type_ in search_types]
# Tricky
ids = sorted(reduce(set.union, container))
return _grab_filtered_id_range(req_n, ids)
get_fit_id_range回傳某些search_types下,一個當下可用的id_range。
# id_grabbers.py
def get_fit_mix_id_range(req_n, search_types, deck=None):
start, *_ = _grab_mix_id_range(req_n, search_types, deck)
return start, start+req_n
get_id回傳某些search_types下,當下可用的最小id。
# id_grabbers.py
def get_mix_id(search_types, deck=None):
start, _ = get_fit_mix_id_range(1, search_types, deck)
return start
get_mat_prop_id回傳一個在同時考慮material及property情況下的最小可用id。這可以幫助我們在建立Entity時,有同樣的material及property id。
# id_grabbers.py
def get_mat_prop_id(deck=None):
search_types = [LSDYNAType.MATERIAL.value, LSDYNAType.PROPERTY.value]
return get_mix_id(search_types, deck)