Skip to content

Commit

Permalink
Sensitivity analysis methods (#51)
Browse files Browse the repository at this point in the history
Sensitivity analysis added to the core

Co-authored-by: Denis S <[email protected]>
  • Loading branch information
DenisSidoren and Denis S authored Sep 2, 2023
1 parent b3a9fc9 commit 4f9c34c
Show file tree
Hide file tree
Showing 30 changed files with 1,377 additions and 140 deletions.
31 changes: 25 additions & 6 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
# .readthedocs.yml
# Read the Docs configuration file
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Build documentation in the docs/ directory with Sphinx
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.7"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
version: 3.7
install:
- requirements: requirements.txt
install:
- requirements: requirements.txt
3 changes: 3 additions & 0 deletions cases/main_conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import argparse
from gefest.core.utils import project_root
root = project_root()
file = 'optimized.pkl'

"""
General configurations for all cases.
Expand Down
File renamed without changes.
Empty file.
55 changes: 55 additions & 0 deletions cases/sensitivity_analysis/configuration_sa/sa_domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import numpy as np

from gefest.core.geometry.geometry_2d import Geometry2D
from gefest.core.opt.setup import Setup
from gefest.core.structure.domain import Domain
from gefest.core.structure.prohibited import create_prohibited

# ------------
# USER-DEFINED CONFIGURATION OF DOMAIN FOR BREAKWATERS TASK
# ------------

grid_resolution_x = 83 # Number of points on x-axis
grid_resolution_y = 58 # Number of points on y-axis
coord_X = np.linspace(0, 2075, grid_resolution_x + 1) # X coordinate for spatial grid
coord_Y = np.linspace(0, 1450, grid_resolution_y + 1) # Y coordinate for spatial grid

grid = [grid_resolution_x, grid_resolution_y] # points grid
targets = [[49, 26], [11, 37], [5, 60]] # grid coordinates of considered targets

"""
Prohibited objects
"""
fixed_targets = [[coord_X[26], coord_Y[49]], [coord_X[37], coord_Y[11]], [coord_X[60], coord_Y[5]]]

# Creation of prohibited structure consist of targets, lines, areas
prohibited_structure = create_prohibited(targets=fixed_targets)


def configurate_domain(poly_num: int,
points_num: int,
is_closed: bool):
# ------------
# GEFEST domain based on user-defined configuration_de
# ------------
if is_closed:
min_points_num = 3
else:
min_points_num = 2

geometry = Geometry2D(is_closed=is_closed)
domain = Domain(allowed_area=[(min(coord_X), min(coord_Y)),
(min(coord_X), max(coord_Y)),
(max(coord_X), max(coord_Y)),
(max(coord_X), min(coord_Y)),
(min(coord_X), min(coord_Y))],
prohibited_area=prohibited_structure,
geometry=geometry,
max_poly_num=poly_num,
min_poly_num=1,
max_points_num=points_num,
min_points_num=min_points_num,
is_closed=is_closed)
task_setup = Setup(domain=domain)

return domain, task_setup
47 changes: 47 additions & 0 deletions cases/sensitivity_analysis/configuration_sa/sa_estimator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import numpy as np

from gefest.core.structure.structure import Structure
from gefest.tools.estimators.simulators.swan.swan_interface import Swan
from gefest.tools.estimators.estimator import Estimator
from gefest.core.utils import project_root
import cases.breakwaters.configuration_de.bw_domain as area


def configurate_estimator(domain, path_sim=False):
# ------------
# User-defined estimator
# it should be created as object with .estimate() method
# ------------
if not path_sim:
root_path = project_root()
path_sim = f'{root_path}/gefest/tools/estimators/simulators/swan/swan_model/'

swan = Swan(path=path_sim,
targets=area.targets,
grid=area.grid,
domain=domain)

# Multi-criteria loss function, in our case - HW and cost of structure
def loss(struct: Structure, estimator):
max_length = np.linalg.norm(
np.array([max(area.coord_X) - min(area.coord_X), max(area.coord_Y) - min(area.coord_Y)]))
lengths = 0
for poly in struct.polygons:
if poly.id != 'fixed':
length = domain.geometry.get_length(poly)
lengths += length

_, hs = estimator.estimate(struct)
l_f = [hs, 2 * lengths / max_length]

return l_f

# ------------
# GEFEST estimator
# ------------

# Here loss is an optional argument, otherwise estimator will be considered as loss for minimizing
estimator = Estimator(estimator=swan,
loss=loss)

return estimator
28 changes: 28 additions & 0 deletions cases/sensitivity_analysis/configuration_sa/sa_optimizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from gefest.tools.optimizers.SPEA2.SPEA2 import SPEA2
from gefest.core.opt.operators.operators import sensitivity_operators
from gefest.tools.optimizers.optimizer import Optimizer


def configurate_optimizer(pop_size: int,
crossover_rate: int,
mutation_rate: int,
task_setup):
# ------------
# User-defined optimizer (SPEA2 in this case)
# it should be created as object with .step() method
# ------------
params = SPEA2.Params(pop_size=pop_size,
crossover_rate=crossover_rate,
mutation_rate=mutation_rate,
mutation_value_rate=[])

spea2_optimizer = SPEA2(params=params,
evolutionary_operators=sensitivity_operators(),
task_setup=task_setup)

# ------------
# GEFEST optimizer
# ------------
optimizer = Optimizer(optimizer=spea2_optimizer)

return optimizer
19 changes: 19 additions & 0 deletions cases/sensitivity_analysis/configuration_sa/sa_sampler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from gefest.tools.samplers.sens_analysis.sens_sampler import SensitivitySampler
from gefest.tools.samplers.sampler import Sampler


def configurate_sampler(domain, path):
# ------------
# User-defined sampler
# it should be created as object with .sample() method
# ------------
sensitivity_sampler = SensitivitySampler(path=path)

# ------------
# GEFEST sampler,
# it consist of user defined sampler and configurated domain
# ------------
sampler = Sampler(sampler=sensitivity_sampler,
domain=domain)

return sampler
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pathlib import Path

from gefest.core.structure.structure import Structure
from gefest.tools.estimators.DL.bw_surrogate.bw_cnn import BWCNN
from gefest.tools.estimators.simulators.swan.swan_interface import Swan
from gefest.tools.estimators.estimator import Estimator
import cases.breakwaters.configuration_de.bw_domain as area


def configurate_estimator(domain, path_sim=False, path_sur=False):
if not path_sim:
root_path = Path(__file__).parent.parent.parent.parent
path_sim = f"{root_path}/gefest/tools/estimators/simulators/swan/swan_model/"

if not path_sur:
root_path = Path(__file__).parent.parent.parent.parent
path_sur = f"{root_path}/gefest/tools/estimators/DL/bw_surrogate/bw_surrogate_700_train.h5"

swan = Swan(path=path_sim, targets=area.targets, grid=area.grid, domain=domain)

cnn = BWCNN(domain=domain, path=path_sur, main_model=swan)

# Multi-criteria loss function, in our case - HW and cost of structure
def loss(struct: Structure, estimator):
hs = estimator.estimate(struct)
return hs

# ------------
# GEFEST estimator
# ------------

# Here loss is an optional argument, otherwise estimator will be considered as loss for minimizing
estimator = Estimator(estimator=cnn, loss=loss)

return estimator
44 changes: 44 additions & 0 deletions cases/sensitivity_analysis/creator_structures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from gefest.core.opt.gen_design import design
from cases.breakwaters.configuration_de import bw_optimizer, bw_sampler
from cases.main_conf import opt_params
from cases.sensitivity_analysis.configuration_sa import (
sa_domain,
sa_surrogate_estimator,
)

opt_params.path_to_sim = False
opt_params.path_to_sur = False
opt_params.pop_size = 20
opt_params.n_steps = 50

domain, task_setup = sa_domain.configurate_domain(
poly_num=opt_params.n_polys,
points_num=opt_params.n_points,
is_closed=opt_params.is_closed,
)

estimator = sa_surrogate_estimator.configurate_estimator(
domain=domain, path_sim=opt_params.path_to_sim
)

sampler = bw_sampler.configurate_sampler(domain=domain)

optimizer = bw_optimizer.configurate_optimizer(
pop_size=opt_params.pop_size,
crossover_rate=opt_params.c_rate,
mutation_rate=opt_params.m_rate,
task_setup=task_setup,
)


# if __name__ == "__main__":
def get_structure(n_steps=opt_params.n_steps, pop_size=opt_params.pop_size):
optimized_population = design(
n_steps=n_steps,
pop_size=pop_size,
estimator=estimator,
sampler=sampler,
optimizer=optimizer,
extra=True,
)
return optimized_population
64 changes: 64 additions & 0 deletions cases/sensitivity_analysis/sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from gefest.core.algs.sensitivity.sa_core import SA, report_viz
from gefest.core.opt.gen_design import design
from cases.breakwaters.configuration_de import bw_optimizer, bw_sampler
from cases.main_conf import opt_params
from cases.sensitivity_analysis.configuration_sa import (
sa_domain,
sa_surrogate_estimator,
)

# ------------
# Generative design stage
# ------------
# Configurations for evolutionary optimization
opt_params.path_to_sim = False
opt_params.path_to_sur = False
opt_params.pop_size = 20
opt_params.n_steps = 50

domain, task_setup = sa_domain.configurate_domain(
poly_num=opt_params.n_polys,
points_num=opt_params.n_points,
is_closed=opt_params.is_closed,
)

estimator = sa_surrogate_estimator.configurate_estimator(
domain=domain, path_sim=opt_params.path_to_sim
)

sampler = bw_sampler.configurate_sampler(domain=domain)

optimizer = bw_optimizer.configurate_optimizer(
pop_size=opt_params.pop_size,
crossover_rate=opt_params.c_rate,
mutation_rate=opt_params.m_rate,
task_setup=task_setup,
)


optimized_structure = design(
n_steps=opt_params.n_steps,
pop_size=opt_params.pop_size,
estimator=estimator,
sampler=sampler,
optimizer=optimizer,
extra=True,
)


# ------------
# Sensitivity-based optimization
# ------------

sens_optimizer = SA(
optimized_pop=optimized_structure, estimator=estimator, domain=domain
)

# For receiving only the improved structure
# improved_structure = sens_optimizer.get_improved_structure

# For receiving full history of optimization and further visualization
sens_results = sens_optimizer.analysis()

# save report image to the main directory of lib
report_viz(analysis_result=sens_results)
Binary file removed docs/img/structure_plot.png
Binary file not shown.
2 changes: 1 addition & 1 deletion gefest/core/algs/geom/validation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Here are defined general constraints on polygons by validation rules.
Validation is a checking on valid and unvalid objects for further processing.
Validation is a checking on valid and invalid objects for further processing.
"""

from shapely.geometry import Point as GeomPoint, Polygon as GeomPolygon
Expand Down
Empty file.
Loading

0 comments on commit 4f9c34c

Please sign in to comment.