from fconcrete.helpers import printProgressBar
import numpy as np
import pandas as pd
import time
[docs]class Analysis:
[docs] @staticmethod
def create_samples(kwargs, sort_by_multiplication, must_test_for_each):
possible_values = []
for kwarg_value in kwargs.values():
possible_values = [*possible_values, np.arange(*kwarg_value)] if (len(kwarg_value) == 3 and type(kwarg_value)==tuple) else [*possible_values, kwarg_value]
combinations = np.array(np.meshgrid(*possible_values)).T.reshape(-1,len(possible_values))
combinations_table = pd.DataFrame(combinations, columns=kwargs.keys())
# sorting the combinations
if sort_by_multiplication:
combinations_table["multiply"] = combinations_table.apply(lambda row: np.array([ v for k, v in row.to_dict().items() if k not in must_test_for_each ]).prod(), axis=1)
combinations_table = combinations_table.sort_values(by=[*must_test_for_each, "multiply"])
combinations_table = combinations_table.drop(columns="multiply")
else:
combinations_table = combinations_table.sort_values(by=must_test_for_each)
combinations_table.reset_index(inplace=True, drop=True)
return combinations_table
@staticmethod
def _checkNextStep(combination_kwarg, step, must_test_for_each, combinations_table):
current_must_test_for_each = { k: v for k, v in combination_kwarg.items() if k in must_test_for_each }
current_to_test_table = combinations_table.loc[step+1:]
if len(current_to_test_table)==0: return
step = np.inf
for k in current_must_test_for_each.keys():
new_table = current_to_test_table[current_to_test_table[k] > combination_kwarg[k]]
if len(new_table)==0: return
new_index_p = new_table.iloc[0].name
step = min(step, new_index_p)
if len(new_table)==0: return
return step
[docs] @staticmethod
def getBestSolution(concrete_beam_function,
max_steps_without_decrease = float("inf"),
avoid_estimate=False,
show_progress=True,
sort_by_multiplication=False,
must_test_for_each=[],
**kwargs):
r"""
Returns a report with all materials and cost.
Call signatures:
`fc.Analysis.getBestSolution(concrete_beam_function,
... max_steps_without_decrease = float("inf"),
... avoid_estimate=False,
... show_progress=True,
... sort_by_multiplication=False,
... **kwargs)`
>>> def concrete_beam_function(width, height, length):
... slab_area = 5*5
... kn_per_m2 = 5
... distributed_load = -slab_area*kn_per_m2/500
... pp = fc.Load.UniformDistributedLoad(-width*height*25/1000000, x_begin=0, x_end=length)
... n1 = fc.Node.SimpleSupport(x=0, length=20)
... n2 = fc.Node.SimpleSupport(x=400, length=20)
... f1 = fc.Load.UniformDistributedLoad(-0.01, x_begin=0, x_end=1)
... beam = fc.ConcreteBeam(
... loads = [f1, pp],
... nodes = [n1, n2],
... section = fc.Rectangle(width, height),
... division = 200
... )
... return beam
>>> full_report, solution_report, best_solution = fc.Analysis.getBestSolution(concrete_beam_function,
... max_steps_without_decrease=15,
... sort_by_multiplication=True,
... avoid_estimate=True,
... show_progress=False,
... width=[15],
... height=(30, 34, 2),
... length=[150])
>>> # Table is sorted by cost ascending, so the first one is the most economic solution.
>>> # Alternative way to look to the best solution
>> print(best_solution)
{'width': 15.0, 'height': 30.0, 'length': 150.0, 'cost': 126.2650347902965, 'error': '', 'Concrete': 63.59, 'Longitudinal bar': 35.31, 'Transversal bar': 27.36}
Parameters
----------
concrete_beam_function
Define the function that is going to create the beam given the parameters.
max_steps_without_decrease : int, optional
If the cost has not decrescead after max_steps_without_decrease steps, the loop breaks.
Only use it in case your parameter combination has a logical order.
Default inf.
show_progress : `bool`, optional
Estimate time using the last combination.
If a exception is found, 80s per loop is set and a message about the not precision is shown.
Also show progress bar in percentage.
Default True.
sort_by_multiplication : `bool`, optional
Sort combinations by the multiplication os all parameter. Useful to use with max_steps_without_decrease when the is a logical order.
Default False.
must_test_for_each: list, optional
From the kwargs parameters, define the ones that must be tested for all their values.
Useful, for example, when you want to test for all possible lengths, but not all height and width.
kwargs
Possible arguments for the concrete_beam_function.
If a set of 3 elements is given, np.arange(\*kwarg_value) will be called.
The kwargs must have the same name that the concrete_beam_function expects as arguments.
The combination is made with np.meshgrid.
"""
combinations_table = Analysis.create_samples(kwargs, sort_by_multiplication, must_test_for_each)
total_of_combinations = len(combinations_table)
if avoid_estimate == False:
max_variable_values = combinations_table.iloc[-1].to_dict()
try:
one_precesing_time, not_precise = concrete_beam_function(**max_variable_values).processing_time, False
except:
one_precesing_time, not_precise = 80, True
continue_script = input("There are {} combinations. The estimate time to process all of them is {}s ({} minutes).{}\nType 'y' to continue or another char to cancel.\n".format(total_of_combinations, round(total_of_combinations*one_precesing_time), round(total_of_combinations*one_precesing_time/60), "\nThis measure is not precise!\n" if not_precise else ""))
if avoid_estimate!=False or continue_script=="y":
start_time = time.time()
report = pd.DataFrame(columns=[*list(kwargs.keys()), "cost", "error", 'Concrete', 'Longitudinal bar', 'Transversal bar'])
min_value, steps_without_decrease, step = np.inf, 0, 0
while step<total_of_combinations:
combination_kwarg = combinations_table.loc[step].to_dict()
try:
beam = concrete_beam_function(**combination_kwarg)
error, cost = "", beam.cost
cost_table = list(beam.subtotal_table[:, 1:2].T[0][1:].astype(float))
except Exception as excep:
error, cost, cost_table = str(excep), -1, [-1, -1, -1]
report.loc[step] = [*combination_kwarg.values(), cost, error, *cost_table]
if (cost != -1) and (cost != min(cost, min_value)):
steps_without_decrease += 1
if steps_without_decrease >= max_steps_without_decrease:
step = Analysis._checkNextStep(combination_kwarg, step, must_test_for_each, combinations_table)
if step == None: break
steps_without_decrease = 0
continue
else:
steps_without_decrease, min_value = 0, cost
if show_progress: printProgressBar(step + 1, total_of_combinations, prefix = 'Progress:', suffix = 'Complete', length = 50)
step+=1
full_report = report.copy()
solution_report = report[report["error"] == ""]
solution_report = solution_report.sort_values(by="cost")
if len(solution_report)>0:
if len(must_test_for_each)==0:
best_solution = solution_report.iloc[0]
else:
best_solution = solution_report.drop_duplicates(subset=must_test_for_each)
else:
best_solution = None
end_time = time.time()
if show_progress: printProgressBar(total_of_combinations, total_of_combinations, prefix = 'Progress:', suffix = 'Complete', length = 50)
if show_progress: print("Executed in {}s".format(end_time-start_time))
return full_report, solution_report, best_solution