madrs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- madrs-0.1.0/PKG-INFO +133 -0
- madrs-0.1.0/README.md +110 -0
- madrs-0.1.0/madrs/__init__.py +23 -0
- madrs-0.1.0/madrs/core.py +321 -0
- madrs-0.1.0/madrs.egg-info/PKG-INFO +133 -0
- madrs-0.1.0/madrs.egg-info/SOURCES.txt +9 -0
- madrs-0.1.0/madrs.egg-info/dependency_links.txt +1 -0
- madrs-0.1.0/madrs.egg-info/requires.txt +3 -0
- madrs-0.1.0/madrs.egg-info/top_level.txt +1 -0
- madrs-0.1.0/pyproject.toml +36 -0
- madrs-0.1.0/setup.cfg +4 -0
madrs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: madrs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modified Acceleration-Displacement Response Spectrum (MADRS) seismic analysis method
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/YOUR_USERNAME/MADRS
|
|
7
|
+
Project-URL: Issues, https://github.com/YOUR_USERNAME/MADRS/issues
|
|
8
|
+
Keywords: seismic,structural-engineering,pushover,ADRS,MADRS
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: scipy>=1.10
|
|
22
|
+
Requires-Dist: matplotlib>=3.7
|
|
23
|
+
|
|
24
|
+
# MADRS — Modified Acceleration-Displacement Response Spectrum
|
|
25
|
+
|
|
26
|
+
A Python package implementing the **MADRS** method of FEMA 440 for seismic performance-point calculation using pushover curves and response spectra.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### From PyPI (once published)
|
|
33
|
+
```bash
|
|
34
|
+
pip install madrs
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Directly from GitHub
|
|
38
|
+
```bash
|
|
39
|
+
pip install git+https://github.com/YOUR_USERNAME/MADRS.git
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Local development install (editable)
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/YOUR_USERNAME/MADRS.git
|
|
45
|
+
cd MADRS
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import numpy as np
|
|
55
|
+
from madrs import MADRS_Method
|
|
56
|
+
|
|
57
|
+
# --- Pushover curve: columns = [displacement (m), base shear (kN)] ---
|
|
58
|
+
PO = np.loadtxt("pushover.csv", delimiter=",")
|
|
59
|
+
|
|
60
|
+
# --- Demand curve: columns = [period (s), Sa (g)] ---
|
|
61
|
+
DC = np.loadtxt("spectrum.csv", delimiter=",")
|
|
62
|
+
|
|
63
|
+
# --- Modal / structural parameters ---
|
|
64
|
+
pf1 = 1.30 # Modal participation factor (1st mode)
|
|
65
|
+
alpha1 = 0.85 # Modal mass coefficient (1st mode)
|
|
66
|
+
wt = 5000.0 # Seismic weight (kN)
|
|
67
|
+
phi_roof1 = 1.0 # 1st-mode shape at roof
|
|
68
|
+
|
|
69
|
+
tol = 1e-4 # Area-balance tolerance for bilinear fitting
|
|
70
|
+
CP1 = 0.5 # Lower ay search bound (fraction of api)
|
|
71
|
+
CP2 = 0.95 # Upper ay search bound (fraction of api)
|
|
72
|
+
|
|
73
|
+
results = MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2)
|
|
74
|
+
|
|
75
|
+
dpi, api, dy, ay, roof_disp, flag, *_ = results
|
|
76
|
+
|
|
77
|
+
if flag:
|
|
78
|
+
print(f"Performance point — Sd: {dpi:.4f} m | Sa: {api:.4f} g")
|
|
79
|
+
print(f"Roof displacement : {roof_disp:.4f} m")
|
|
80
|
+
else:
|
|
81
|
+
print("No performance point found — adjust CP1, CP2, or tol.")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### `MADRS_Method`
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2,
|
|
92
|
+
show_intermediate_plots=True)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Parameter | Type | Description |
|
|
96
|
+
|-----------|------|-------------|
|
|
97
|
+
| `PO` | `ndarray (N, 2)` | Pushover curve — `[displacement (m), base shear (kN)]` |
|
|
98
|
+
| `DC` | `ndarray (M, 2)` | Demand/response spectrum — `[period (s), Sa (g)]` |
|
|
99
|
+
| `pf1` | `float` | Modal participation factor (1st mode) |
|
|
100
|
+
| `alpha1` | `float` | Modal mass coefficient (1st mode) |
|
|
101
|
+
| `wt` | `float` | Seismic weight (kN) |
|
|
102
|
+
| `phi_roof1` | `float` | 1st-mode shape value at roof |
|
|
103
|
+
| `tol` | `float` | Tolerance for bilinear area balance |
|
|
104
|
+
| `CP1` | `float` | Lower multiplier for `ay` search |
|
|
105
|
+
| `CP2` | `float` | Upper multiplier for `ay` search |
|
|
106
|
+
| `show_intermediate_plots` | `bool` | Show 6-panel diagnostic figure (default `True`) |
|
|
107
|
+
|
|
108
|
+
**Returns** `(dpi, api, dy, ay, roof_disp, flag, Sd_spectra, Sa_Spectranew, Sd, Sa, x_bilinear, y_bilinear)`
|
|
109
|
+
|
|
110
|
+
### Helper functions
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from madrs import curve_intersections, closest_point_on_curve, area_between_curves
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- **`curve_intersections(x1, y1, x2, y2)`** — find intersection points between two polylines
|
|
117
|
+
- **`closest_point_on_curve(P, x_curve, y_curve)`** — nearest point on a polyline to point P
|
|
118
|
+
- **`area_between_curves(x, y1, y2)`** — signed area between two curves via Simpson's rule
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Requirements
|
|
123
|
+
|
|
124
|
+
- Python ≥ 3.9
|
|
125
|
+
- numpy ≥ 1.24
|
|
126
|
+
- scipy ≥ 1.10
|
|
127
|
+
- matplotlib ≥ 3.7
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
madrs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# MADRS — Modified Acceleration-Displacement Response Spectrum
|
|
2
|
+
|
|
3
|
+
A Python package implementing the **MADRS** method of FEMA 440 for seismic performance-point calculation using pushover curves and response spectra.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### From PyPI (once published)
|
|
10
|
+
```bash
|
|
11
|
+
pip install madrs
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Directly from GitHub
|
|
15
|
+
```bash
|
|
16
|
+
pip install git+https://github.com/YOUR_USERNAME/MADRS.git
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Local development install (editable)
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/YOUR_USERNAME/MADRS.git
|
|
22
|
+
cd MADRS
|
|
23
|
+
pip install -e .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import numpy as np
|
|
32
|
+
from madrs import MADRS_Method
|
|
33
|
+
|
|
34
|
+
# --- Pushover curve: columns = [displacement (m), base shear (kN)] ---
|
|
35
|
+
PO = np.loadtxt("pushover.csv", delimiter=",")
|
|
36
|
+
|
|
37
|
+
# --- Demand curve: columns = [period (s), Sa (g)] ---
|
|
38
|
+
DC = np.loadtxt("spectrum.csv", delimiter=",")
|
|
39
|
+
|
|
40
|
+
# --- Modal / structural parameters ---
|
|
41
|
+
pf1 = 1.30 # Modal participation factor (1st mode)
|
|
42
|
+
alpha1 = 0.85 # Modal mass coefficient (1st mode)
|
|
43
|
+
wt = 5000.0 # Seismic weight (kN)
|
|
44
|
+
phi_roof1 = 1.0 # 1st-mode shape at roof
|
|
45
|
+
|
|
46
|
+
tol = 1e-4 # Area-balance tolerance for bilinear fitting
|
|
47
|
+
CP1 = 0.5 # Lower ay search bound (fraction of api)
|
|
48
|
+
CP2 = 0.95 # Upper ay search bound (fraction of api)
|
|
49
|
+
|
|
50
|
+
results = MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2)
|
|
51
|
+
|
|
52
|
+
dpi, api, dy, ay, roof_disp, flag, *_ = results
|
|
53
|
+
|
|
54
|
+
if flag:
|
|
55
|
+
print(f"Performance point — Sd: {dpi:.4f} m | Sa: {api:.4f} g")
|
|
56
|
+
print(f"Roof displacement : {roof_disp:.4f} m")
|
|
57
|
+
else:
|
|
58
|
+
print("No performance point found — adjust CP1, CP2, or tol.")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
65
|
+
### `MADRS_Method`
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2,
|
|
69
|
+
show_intermediate_plots=True)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Parameter | Type | Description |
|
|
73
|
+
|-----------|------|-------------|
|
|
74
|
+
| `PO` | `ndarray (N, 2)` | Pushover curve — `[displacement (m), base shear (kN)]` |
|
|
75
|
+
| `DC` | `ndarray (M, 2)` | Demand/response spectrum — `[period (s), Sa (g)]` |
|
|
76
|
+
| `pf1` | `float` | Modal participation factor (1st mode) |
|
|
77
|
+
| `alpha1` | `float` | Modal mass coefficient (1st mode) |
|
|
78
|
+
| `wt` | `float` | Seismic weight (kN) |
|
|
79
|
+
| `phi_roof1` | `float` | 1st-mode shape value at roof |
|
|
80
|
+
| `tol` | `float` | Tolerance for bilinear area balance |
|
|
81
|
+
| `CP1` | `float` | Lower multiplier for `ay` search |
|
|
82
|
+
| `CP2` | `float` | Upper multiplier for `ay` search |
|
|
83
|
+
| `show_intermediate_plots` | `bool` | Show 6-panel diagnostic figure (default `True`) |
|
|
84
|
+
|
|
85
|
+
**Returns** `(dpi, api, dy, ay, roof_disp, flag, Sd_spectra, Sa_Spectranew, Sd, Sa, x_bilinear, y_bilinear)`
|
|
86
|
+
|
|
87
|
+
### Helper functions
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from madrs import curve_intersections, closest_point_on_curve, area_between_curves
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- **`curve_intersections(x1, y1, x2, y2)`** — find intersection points between two polylines
|
|
94
|
+
- **`closest_point_on_curve(P, x_curve, y_curve)`** — nearest point on a polyline to point P
|
|
95
|
+
- **`area_between_curves(x, y1, y2)`** — signed area between two curves via Simpson's rule
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- Python ≥ 3.9
|
|
102
|
+
- numpy ≥ 1.24
|
|
103
|
+
- scipy ≥ 1.10
|
|
104
|
+
- matplotlib ≥ 3.7
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
madrs — Modified Acceleration-Displacement Response Spectrum method.
|
|
3
|
+
|
|
4
|
+
Quickly run a seismic performance-point analysis:
|
|
5
|
+
|
|
6
|
+
from madrs import MADRS_Method, curve_intersections, closest_point_on_curve
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .core import (
|
|
10
|
+
MADRS_Method,
|
|
11
|
+
area_between_curves,
|
|
12
|
+
curve_intersections,
|
|
13
|
+
closest_point_on_curve,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"MADRS_Method",
|
|
18
|
+
"area_between_curves",
|
|
19
|
+
"curve_intersections",
|
|
20
|
+
"closest_point_on_curve",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from scipy.integrate import simpson
|
|
4
|
+
|
|
5
|
+
TOL_INTER = 5 # 5% error tolerance at intersection point
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def area_between_curves(x_common, y1_common, y2_common):
|
|
9
|
+
"""Compute the signed area between two curves using Simpson's rule."""
|
|
10
|
+
return simpson(y1_common - y2_common, x=x_common)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def curve_intersections(x1, y1, x2, y2):
|
|
14
|
+
"""
|
|
15
|
+
Find intersection points between two curves defined by (x1, y1) and (x2, y2).
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
list of (x, y) tuples at intersection points
|
|
20
|
+
"""
|
|
21
|
+
x1, y1, x2, y2 = map(np.asarray, (x1, y1, x2, y2))
|
|
22
|
+
y2_interp = np.interp(x1, x2, y2)
|
|
23
|
+
diff = y1 - y2_interp
|
|
24
|
+
sign_changes = np.where(np.diff(np.sign(diff)) != 0)[0]
|
|
25
|
+
|
|
26
|
+
intersections = []
|
|
27
|
+
for idx in sign_changes:
|
|
28
|
+
x_left, x_right = x1[idx], x1[idx + 1]
|
|
29
|
+
y_left, y_right = diff[idx], diff[idx + 1]
|
|
30
|
+
x_inter = x_left - y_left * (x_right - x_left) / (y_right - y_left)
|
|
31
|
+
y_inter = np.interp(x_inter, x1, y1)
|
|
32
|
+
intersections.append((x_inter, y_inter))
|
|
33
|
+
|
|
34
|
+
return intersections
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def closest_point_on_curve(P, x_curve, y_curve):
|
|
38
|
+
"""
|
|
39
|
+
Find the closest point on a polyline curve to a given point P.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
P : array-like, shape (2,)
|
|
44
|
+
x_curve, y_curve : array-like
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
best_point : ndarray, shape (2,)
|
|
49
|
+
min_dist : float
|
|
50
|
+
percent_error : ndarray, shape (2,)
|
|
51
|
+
"""
|
|
52
|
+
P = np.array(P, dtype=float)
|
|
53
|
+
x_curve = np.array(x_curve, dtype=float)
|
|
54
|
+
y_curve = np.array(y_curve, dtype=float)
|
|
55
|
+
|
|
56
|
+
min_dist = np.inf
|
|
57
|
+
best_point = None
|
|
58
|
+
|
|
59
|
+
for i in range(len(x_curve) - 1):
|
|
60
|
+
A = np.array([x_curve[i], y_curve[i]])
|
|
61
|
+
B = np.array([x_curve[i + 1], y_curve[i + 1]])
|
|
62
|
+
AB = B - A
|
|
63
|
+
AP = P - A
|
|
64
|
+
t = np.clip(np.dot(AP, AB) / np.dot(AB, AB), 0, 1)
|
|
65
|
+
Q = A + t * AB
|
|
66
|
+
dist = np.linalg.norm(P - Q)
|
|
67
|
+
|
|
68
|
+
if dist < min_dist:
|
|
69
|
+
min_dist = dist
|
|
70
|
+
best_point = Q
|
|
71
|
+
|
|
72
|
+
if best_point is None:
|
|
73
|
+
raise ValueError("No valid closest point found on curve")
|
|
74
|
+
|
|
75
|
+
percent_error = np.abs((P - best_point) / (best_point + 1e-12)) * 100
|
|
76
|
+
return best_point, min_dist, percent_error
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2, show_intermediate_plots=True):
|
|
80
|
+
"""
|
|
81
|
+
Modified Acceleration-Displacement Response Spectrum (MADRS) method.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
PO : ndarray, shape (N, 2)
|
|
86
|
+
Pushover / capacity curve. Columns: [displacement (m), base shear (kN)].
|
|
87
|
+
DC : ndarray, shape (M, 2)
|
|
88
|
+
Demand curve (elastic response spectrum). Columns: [period (s), Sa (g)].
|
|
89
|
+
pf1 : float
|
|
90
|
+
Modal participation factor for the first mode.
|
|
91
|
+
alpha1 : float
|
|
92
|
+
Modal mass coefficient for the first mode.
|
|
93
|
+
wt : float
|
|
94
|
+
Seismic weight of the structure (kN).
|
|
95
|
+
phi_roof1 : float
|
|
96
|
+
First-mode shape value at roof level.
|
|
97
|
+
tol : float
|
|
98
|
+
Tolerance for area difference in bilinear curve fitting.
|
|
99
|
+
CP1 : float
|
|
100
|
+
Lower bound multiplier for ay search (fraction of api).
|
|
101
|
+
CP2 : float
|
|
102
|
+
Upper bound multiplier for ay search (fraction of api).
|
|
103
|
+
show_intermediate_plots : bool, optional
|
|
104
|
+
If True (default), display the 6-panel diagnostic figure.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
dpi : float or None
|
|
109
|
+
Spectral displacement at performance point (m).
|
|
110
|
+
api : float or None
|
|
111
|
+
Spectral acceleration at performance point (g).
|
|
112
|
+
dy : float or None
|
|
113
|
+
Yield spectral displacement of bilinear curve (m).
|
|
114
|
+
ay : float or None
|
|
115
|
+
Yield spectral acceleration of bilinear curve (g).
|
|
116
|
+
roof_disp : float or None
|
|
117
|
+
Roof displacement at performance point (m).
|
|
118
|
+
flag : int
|
|
119
|
+
1 if a performance point was found, 0 otherwise.
|
|
120
|
+
Sd_spectra : ndarray or None
|
|
121
|
+
Spectral displacements of the demand spectrum.
|
|
122
|
+
Sa_Spectranew : ndarray or None
|
|
123
|
+
Scaled demand spectrum spectral accelerations.
|
|
124
|
+
Sd : ndarray or None
|
|
125
|
+
Capacity spectrum spectral displacements.
|
|
126
|
+
Sa : ndarray or None
|
|
127
|
+
Capacity spectrum spectral accelerations.
|
|
128
|
+
x_bilinear : ndarray or None
|
|
129
|
+
x-coordinates of fitted bilinear curve.
|
|
130
|
+
y_bilinear : ndarray or None
|
|
131
|
+
y-coordinates of fitted bilinear curve.
|
|
132
|
+
"""
|
|
133
|
+
fig, axes = plt.subplots(2, 3, figsize=(15, 9))
|
|
134
|
+
axes = axes.ravel()
|
|
135
|
+
|
|
136
|
+
# --- Capacity curve ---
|
|
137
|
+
pushover_curve = PO
|
|
138
|
+
axes[0].plot(pushover_curve[:, 0], pushover_curve[:, 1],
|
|
139
|
+
label="Capacity Curve", color="blue")
|
|
140
|
+
axes[0].set_xlabel("Displacement (m)", fontsize=12)
|
|
141
|
+
axes[0].set_ylabel("Base Shear (kN)", fontsize=12)
|
|
142
|
+
axes[0].legend(loc="lower right", framealpha=0.0, fontsize=10)
|
|
143
|
+
|
|
144
|
+
# --- Truncate at 0.8 * RF_max ---
|
|
145
|
+
rf_max = np.max(pushover_curve[:, 1])
|
|
146
|
+
index = np.argmax(pushover_curve[:, 1])
|
|
147
|
+
narray = pushover_curve[index:, 1]
|
|
148
|
+
narray2 = narray - 0.8 * rf_max
|
|
149
|
+
index_min = np.argmin(np.abs(narray2))
|
|
150
|
+
po_y = pushover_curve[: index + index_min, 1]
|
|
151
|
+
po_x = pushover_curve[: index + index_min, 0]
|
|
152
|
+
|
|
153
|
+
axes[1].plot(po_x, po_y, label="Capacity Curve up to 0.8*Max", color="green")
|
|
154
|
+
axes[1].set_xlabel("Displacement (m)", fontsize=12)
|
|
155
|
+
axes[1].set_ylabel("Base Shear (kN)", fontsize=12)
|
|
156
|
+
axes[1].legend(loc="lower right", framealpha=0.0, fontsize=10)
|
|
157
|
+
|
|
158
|
+
# --- Capacity spectrum (Sa, Sd) ---
|
|
159
|
+
Sa = po_y / (wt * alpha1)
|
|
160
|
+
Sd = po_x / (pf1 * phi_roof1)
|
|
161
|
+
axes[2].plot(Sd, Sa, label="Capacity Spectrum", color="blue")
|
|
162
|
+
axes[2].set_xlabel("Spectral Displacement (m)", fontsize=12)
|
|
163
|
+
axes[2].set_ylabel("Spectral Acceleration (g)", fontsize=12)
|
|
164
|
+
axes[2].legend(loc="lower right", framealpha=0.0, fontsize=10)
|
|
165
|
+
|
|
166
|
+
# --- Demand curve ---
|
|
167
|
+
EC_Spectrum = DC
|
|
168
|
+
axes[3].plot(EC_Spectrum[:, 0], EC_Spectrum[:, 1],
|
|
169
|
+
label="Demand Curve", color="red")
|
|
170
|
+
axes[3].set_xlabel("Time Period (Sec)", fontsize=12)
|
|
171
|
+
axes[3].set_ylabel("Spectral Acceleration (g)", fontsize=12)
|
|
172
|
+
axes[3].legend(loc="lower right", framealpha=0.0, fontsize=10)
|
|
173
|
+
|
|
174
|
+
# --- Demand spectrum in Sa-Sd space ---
|
|
175
|
+
Sd_spectra = EC_Spectrum[:, 1] * (EC_Spectrum[:, 0] ** 2) / (4 * np.pi ** 2)
|
|
176
|
+
axes[4].plot(Sd_spectra, EC_Spectrum[:, 1],
|
|
177
|
+
label="Demand Spectrum", color="orange")
|
|
178
|
+
axes[4].set_xlabel("Spectral Displacement (m)", fontsize=12)
|
|
179
|
+
axes[4].set_ylabel("Spectral Acceleration (g)", fontsize=12)
|
|
180
|
+
axes[4].legend(loc="lower right", framealpha=0.0, fontsize=10)
|
|
181
|
+
|
|
182
|
+
# --- Initial slope & initial guess ---
|
|
183
|
+
Sa_per_ind = np.argmin(np.abs(Sa - 0.6 * Sa.max()))
|
|
184
|
+
k_init = (Sa[Sa_per_ind] - Sa[0]) / (Sd[Sa_per_ind] - Sd[0])
|
|
185
|
+
x2 = np.array([0, 1 / k_init])
|
|
186
|
+
y2 = np.array([0, 1])
|
|
187
|
+
|
|
188
|
+
points = curve_intersections(Sd_spectra, EC_Spectrum[:, 1], x2, y2)
|
|
189
|
+
print("Intersection points:", points)
|
|
190
|
+
|
|
191
|
+
x3 = np.array([points[0][0], points[0][0]])
|
|
192
|
+
y3 = np.array([0, points[0][1]])
|
|
193
|
+
points2 = curve_intersections(Sd, Sa, x3, y3)
|
|
194
|
+
x4 = points2[-1][0]
|
|
195
|
+
y4 = points2[-1][1]
|
|
196
|
+
|
|
197
|
+
axes[5].plot(Sd, Sa, label="Capacity Spectrum", color="blue")
|
|
198
|
+
axes[5].plot(Sd_spectra, EC_Spectrum[:, 1],
|
|
199
|
+
label="Demand Spectrum", color="orange")
|
|
200
|
+
axes[5].plot(x2, y2, label="Initial Slope")
|
|
201
|
+
axes[5].plot(points[0][0], points[0][1], "ro", label="Intersection")
|
|
202
|
+
axes[5].plot(x4, y4, "go", label="Initial Guess")
|
|
203
|
+
axes[5].set_xlabel("Spectral Displacement (m)", fontsize=12)
|
|
204
|
+
axes[5].set_ylabel("Spectral Acceleration (g)", fontsize=12)
|
|
205
|
+
axes[5].legend(loc="upper right", framealpha=0.0, fontsize=10)
|
|
206
|
+
|
|
207
|
+
for ax in axes:
|
|
208
|
+
ax.set_xlim(left=0)
|
|
209
|
+
ax.set_ylim(bottom=0)
|
|
210
|
+
|
|
211
|
+
plt.tight_layout()
|
|
212
|
+
|
|
213
|
+
if show_intermediate_plots:
|
|
214
|
+
plt.show()
|
|
215
|
+
else:
|
|
216
|
+
plt.close(fig)
|
|
217
|
+
|
|
218
|
+
# --- Main MADRS iteration ---
|
|
219
|
+
loc = np.argmin(abs(Sa - points2[-1][1]))
|
|
220
|
+
|
|
221
|
+
for newloc in np.arange(int(0.1 * loc), len(Sa), 2):
|
|
222
|
+
x4 = Sd[newloc]
|
|
223
|
+
y4 = Sa[newloc]
|
|
224
|
+
|
|
225
|
+
for i in np.arange(CP1 * y4, CP2 * y4, (CP2 - CP1) * y4 / 100):
|
|
226
|
+
x = np.array([0])
|
|
227
|
+
y = np.array([0])
|
|
228
|
+
y = np.append(y, i)
|
|
229
|
+
x = np.append(x, y[-1] / k_init)
|
|
230
|
+
x = np.append(x, x4)
|
|
231
|
+
y = np.append(y, y4)
|
|
232
|
+
|
|
233
|
+
x_common = np.unique(np.concatenate([x, Sd[:newloc]]))
|
|
234
|
+
x_common = x_common[x_common <= x[-1]]
|
|
235
|
+
Sa_common = np.interp(x_common, Sd, Sa)
|
|
236
|
+
y_common = np.interp(x_common, x, y)
|
|
237
|
+
A_diff = area_between_curves(x_common, Sa_common, y_common)
|
|
238
|
+
|
|
239
|
+
if abs(A_diff) < tol:
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
api = y[-1]
|
|
243
|
+
dpi = x[-1]
|
|
244
|
+
ay = y[1]
|
|
245
|
+
dy = x[1]
|
|
246
|
+
mue = dpi / dy
|
|
247
|
+
|
|
248
|
+
beta_zero = 5
|
|
249
|
+
T_zero = 2 * np.pi * (dy / ay) ** 0.5
|
|
250
|
+
|
|
251
|
+
if mue < 4:
|
|
252
|
+
T_eff = (0.2 * (mue - 1) ** 2 - 0.038 * (mue - 1) ** 3 + 1) * T_zero
|
|
253
|
+
elif 4 <= mue <= 6.5:
|
|
254
|
+
T_eff = (0.28 + 0.13 * (mue - 1) + 1) * T_zero
|
|
255
|
+
else:
|
|
256
|
+
T_eff = (0.89 * (((mue - 1) / (1 + 0.05 * (mue - 2))) ** 0.5 - 1) + 1) * T_zero
|
|
257
|
+
|
|
258
|
+
if mue < 4:
|
|
259
|
+
beta_eff = 4.9 * (mue - 1) ** 2 - 1.1 * (mue - 1) ** 3 + beta_zero
|
|
260
|
+
elif 4 <= mue <= 6.5:
|
|
261
|
+
beta_eff = 14 + 0.32 * (mue - 1) + beta_zero
|
|
262
|
+
else:
|
|
263
|
+
beta_eff = (
|
|
264
|
+
19
|
|
265
|
+
* ((0.64 * (mue - 1) - 1) / ((0.64 * (mue - 1)) ** 2))
|
|
266
|
+
* ((T_eff / T_zero) ** 2)
|
|
267
|
+
+ beta_zero
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
alpha = ((api - ay) / (dpi - dy)) / (ay / dy)
|
|
271
|
+
B_beta_eff = 4 / (5.6 - np.log(beta_eff))
|
|
272
|
+
T_sec = T_zero / ((1 + alpha * (mue - 1)) / mue) ** 0.5
|
|
273
|
+
M = (T_eff / T_sec) ** 2
|
|
274
|
+
|
|
275
|
+
Sa_Spectranew = (EC_Spectrum[:, 1] / B_beta_eff) * M
|
|
276
|
+
|
|
277
|
+
P = [x[-1], y[-1]]
|
|
278
|
+
Q, error, percent_error = closest_point_on_curve(P, Sd_spectra, Sa_Spectranew)
|
|
279
|
+
|
|
280
|
+
if percent_error[0] <= TOL_INTER and percent_error[1] <= TOL_INTER:
|
|
281
|
+
flag = 1
|
|
282
|
+
|
|
283
|
+
fig2, ax2 = plt.subplots(figsize=(8, 6))
|
|
284
|
+
ax2.plot(Sd_spectra, EC_Spectrum[:, 1],
|
|
285
|
+
label="Demand Spectrum (EC2)", color="orange")
|
|
286
|
+
ax2.plot(Sd_spectra, Sa_Spectranew,
|
|
287
|
+
label="Demand Spectrum (Scaled)", color="green")
|
|
288
|
+
ax2.plot(Sd, Sa, label="Capacity Spectrum", color="blue")
|
|
289
|
+
ax2.plot(x, y, label="Bilinear Curve", color="cyan")
|
|
290
|
+
ax2.plot(x[-1], y[-1], "ro", label="api, dpi")
|
|
291
|
+
ax2.plot(x[1], y[1], "go", label="ay, dy")
|
|
292
|
+
ax2.legend()
|
|
293
|
+
ax2.set_title("MADRS Approach")
|
|
294
|
+
ax2.set_xlim(left=0)
|
|
295
|
+
ax2.set_ylim(bottom=0)
|
|
296
|
+
ax2.set_xlabel("Spectral displacement (m)")
|
|
297
|
+
ax2.set_ylabel("Spectral acceleration (g)")
|
|
298
|
+
plt.tight_layout()
|
|
299
|
+
plt.show()
|
|
300
|
+
|
|
301
|
+
print("Passed")
|
|
302
|
+
print("Area difference (curve1 - curve2) =", A_diff)
|
|
303
|
+
|
|
304
|
+
BOLD_RED = "\033[1;31m"
|
|
305
|
+
RESET = "\033[0m"
|
|
306
|
+
if abs(A_diff) > tol:
|
|
307
|
+
print(f"{BOLD_RED}Area difference is more than tolerance, "
|
|
308
|
+
f"change bi-linear curve style{RESET}")
|
|
309
|
+
|
|
310
|
+
print("Mue =", mue)
|
|
311
|
+
print("Effective Damping =", beta_eff)
|
|
312
|
+
roof_disp = x[-1] * (pf1 * phi_roof1)
|
|
313
|
+
print("Roof displacement at performance point (m) =", roof_disp)
|
|
314
|
+
|
|
315
|
+
return dpi, api, dy, ay, roof_disp, flag, Sd_spectra, Sa_Spectranew, Sd, Sa, x, y
|
|
316
|
+
|
|
317
|
+
else:
|
|
318
|
+
flag = 0
|
|
319
|
+
|
|
320
|
+
print("No performance point found. Try adjusting CP1, CP2, or tol.")
|
|
321
|
+
return None, None, None, None, None, 0, None, None, None, None, None, None
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: madrs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modified Acceleration-Displacement Response Spectrum (MADRS) seismic analysis method
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/YOUR_USERNAME/MADRS
|
|
7
|
+
Project-URL: Issues, https://github.com/YOUR_USERNAME/MADRS/issues
|
|
8
|
+
Keywords: seismic,structural-engineering,pushover,ADRS,MADRS
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: scipy>=1.10
|
|
22
|
+
Requires-Dist: matplotlib>=3.7
|
|
23
|
+
|
|
24
|
+
# MADRS — Modified Acceleration-Displacement Response Spectrum
|
|
25
|
+
|
|
26
|
+
A Python package implementing the **MADRS** method of FEMA 440 for seismic performance-point calculation using pushover curves and response spectra.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### From PyPI (once published)
|
|
33
|
+
```bash
|
|
34
|
+
pip install madrs
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Directly from GitHub
|
|
38
|
+
```bash
|
|
39
|
+
pip install git+https://github.com/YOUR_USERNAME/MADRS.git
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Local development install (editable)
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/YOUR_USERNAME/MADRS.git
|
|
45
|
+
cd MADRS
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import numpy as np
|
|
55
|
+
from madrs import MADRS_Method
|
|
56
|
+
|
|
57
|
+
# --- Pushover curve: columns = [displacement (m), base shear (kN)] ---
|
|
58
|
+
PO = np.loadtxt("pushover.csv", delimiter=",")
|
|
59
|
+
|
|
60
|
+
# --- Demand curve: columns = [period (s), Sa (g)] ---
|
|
61
|
+
DC = np.loadtxt("spectrum.csv", delimiter=",")
|
|
62
|
+
|
|
63
|
+
# --- Modal / structural parameters ---
|
|
64
|
+
pf1 = 1.30 # Modal participation factor (1st mode)
|
|
65
|
+
alpha1 = 0.85 # Modal mass coefficient (1st mode)
|
|
66
|
+
wt = 5000.0 # Seismic weight (kN)
|
|
67
|
+
phi_roof1 = 1.0 # 1st-mode shape at roof
|
|
68
|
+
|
|
69
|
+
tol = 1e-4 # Area-balance tolerance for bilinear fitting
|
|
70
|
+
CP1 = 0.5 # Lower ay search bound (fraction of api)
|
|
71
|
+
CP2 = 0.95 # Upper ay search bound (fraction of api)
|
|
72
|
+
|
|
73
|
+
results = MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2)
|
|
74
|
+
|
|
75
|
+
dpi, api, dy, ay, roof_disp, flag, *_ = results
|
|
76
|
+
|
|
77
|
+
if flag:
|
|
78
|
+
print(f"Performance point — Sd: {dpi:.4f} m | Sa: {api:.4f} g")
|
|
79
|
+
print(f"Roof displacement : {roof_disp:.4f} m")
|
|
80
|
+
else:
|
|
81
|
+
print("No performance point found — adjust CP1, CP2, or tol.")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### `MADRS_Method`
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
MADRS_Method(PO, DC, pf1, alpha1, wt, phi_roof1, tol, CP1, CP2,
|
|
92
|
+
show_intermediate_plots=True)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Parameter | Type | Description |
|
|
96
|
+
|-----------|------|-------------|
|
|
97
|
+
| `PO` | `ndarray (N, 2)` | Pushover curve — `[displacement (m), base shear (kN)]` |
|
|
98
|
+
| `DC` | `ndarray (M, 2)` | Demand/response spectrum — `[period (s), Sa (g)]` |
|
|
99
|
+
| `pf1` | `float` | Modal participation factor (1st mode) |
|
|
100
|
+
| `alpha1` | `float` | Modal mass coefficient (1st mode) |
|
|
101
|
+
| `wt` | `float` | Seismic weight (kN) |
|
|
102
|
+
| `phi_roof1` | `float` | 1st-mode shape value at roof |
|
|
103
|
+
| `tol` | `float` | Tolerance for bilinear area balance |
|
|
104
|
+
| `CP1` | `float` | Lower multiplier for `ay` search |
|
|
105
|
+
| `CP2` | `float` | Upper multiplier for `ay` search |
|
|
106
|
+
| `show_intermediate_plots` | `bool` | Show 6-panel diagnostic figure (default `True`) |
|
|
107
|
+
|
|
108
|
+
**Returns** `(dpi, api, dy, ay, roof_disp, flag, Sd_spectra, Sa_Spectranew, Sd, Sa, x_bilinear, y_bilinear)`
|
|
109
|
+
|
|
110
|
+
### Helper functions
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from madrs import curve_intersections, closest_point_on_curve, area_between_curves
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- **`curve_intersections(x1, y1, x2, y2)`** — find intersection points between two polylines
|
|
117
|
+
- **`closest_point_on_curve(P, x_curve, y_curve)`** — nearest point on a polyline to point P
|
|
118
|
+
- **`area_between_curves(x, y1, y2)`** — signed area between two curves via Simpson's rule
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Requirements
|
|
123
|
+
|
|
124
|
+
- Python ≥ 3.9
|
|
125
|
+
- numpy ≥ 1.24
|
|
126
|
+
- scipy ≥ 1.10
|
|
127
|
+
- matplotlib ≥ 3.7
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
madrs
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "madrs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Modified Acceleration-Displacement Response Spectrum (MADRS) seismic analysis method"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"numpy>=1.24",
|
|
14
|
+
"scipy>=1.10",
|
|
15
|
+
"matplotlib>=3.7",
|
|
16
|
+
]
|
|
17
|
+
keywords = ["seismic", "structural-engineering", "pushover", "ADRS", "MADRS"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Topic :: Scientific/Engineering",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/YOUR_USERNAME/MADRS"
|
|
32
|
+
Issues = "https://github.com/YOUR_USERNAME/MADRS/issues"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["."]
|
|
36
|
+
include = ["madrs*"]
|
madrs-0.1.0/setup.cfg
ADDED