from fconcrete.helpers import printProgressBar
import numpy as np
import pandas as pd
[docs]class Analysis:
[docs] @staticmethod
def getBestSolution(concrete_beam_function,
max_steps_without_decrease = float("inf"),
avoid_estimate=False,
show_progress=True,
sort_by_multiplication=False,
**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.
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.
"""
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))
# sorting the combinations
if sort_by_multiplication:
combinations_t = combinations.T
multiply = np.ones((1,len(combinations)))
for i in combinations_t:
multiply = multiply*i
combinations = np.append(combinations, multiply.T, axis=1)
combinations = combinations[np.argsort(combinations[:,-1])][:, 0:-1]
# combinations to dict
combination_kwargs = [ { key: combination[i] for i, key in enumerate(kwargs.keys()) } for combination in combinations ]
total_of_combinations = len(combination_kwargs)
if avoid_estimate == False:
max_variable_values = combination_kwargs[-1]
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":
report = (["cost", "error", 'Concrete', 'Longitudinal bar', 'Transversal bar'])
report = [[*list(kwargs.keys()), *report]]
min_value, steps_without_decrease = np.inf, 0
for step, combination_kwarg in enumerate(combination_kwargs):
try:
beam = concrete_beam_function(**combination_kwarg)
error = ""
cost = beam.cost
cost_table = list(beam.subtotal_table[:, 1:2].T[0][1:].astype(float))
if cost != min(cost, min_value):
steps_without_decrease += 1
if steps_without_decrease >= max_steps_without_decrease:
if show_progress:
printProgressBar(total_of_combinations, total_of_combinations, prefix = 'Progress:', suffix = 'Complete', length = 50)
print("\n", "Ended in step {} out of {}. Because max_steps_without_decrease was reached.".format(step + 1, total_of_combinations))
break
else:
steps_without_decrease = 0
min_value = cost
except Exception as excep:
error = str(excep)
cost = -1
cost_table = [-1, -1, -1]
if show_progress: printProgressBar(step + 1, total_of_combinations, prefix = 'Progress:', suffix = 'Complete', length = 50)
row = [*combination_kwarg.values(), cost, error, *cost_table]
report = [*report, row]
full_report = pd.DataFrame(report)
full_report.columns = full_report.loc[0]
full_report = full_report[1:]
solution_report = full_report[full_report["error"] == ""]
solution_report = solution_report.sort_values(by="cost")
best_solution = solution_report.iloc[0,:].to_dict() if len(solution_report)>0 else None
return full_report, solution_report, best_solution